{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# StackingCVClassifier"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "An ensemble-learning meta-classifier for stacking using cross-validation to prepare the inputs for the level-2 classifier to prevent overfitting."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "> from mlxtend.classifier import StackingCVClassifier"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Overview"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Stacking is an ensemble learning technique to combine multiple classification models via a meta-classifier. The `StackingCVClassifier` extends the standard stacking algorithm (implemented as [`StackingClassifier`](StackingClassifier.md)) using cross-validation to prepare the input data for the level-2 classifier. \n",
    "\n",
    "In the standard stacking procedure, the first-level classifiers are fit to the same training set that is used prepare the inputs for the second-level classifier, which may lead to overfitting. The `StackingCVClassifier`, however, uses the concept of cross-validation: the dataset is split into *k* folds, and in *k* successive rounds, *k-1* folds are used to fit the first level classifier; in each round, the first-level classifiers are then applied to the remaining 1 subset that was not used for model fitting in each iteration. The resulting predictions are then stacked and provided -- as input data -- to the second-level classifier. After the training of the `StackingCVClassifier`, the first-level classifiers are fit to the entire dataset as illustrated in the figure below."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![](./StackingCVClassifier_files/stacking_cv_classification_overview.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "More formally, the Stacking Cross-Validation algorithm can be summarized as follows (source: [1]):\n",
    "    \n",
    "![](./StackingCVClassifier_files/stacking_cv_algorithm.png)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### References\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- [1] Tang, J., S. Alelyani, and H. Liu. \"[Data Classification: Algorithms and Applications.](https://books.google.com/books?id=nwQZCwAAQBAJ&lpg=PA500&dq=stacking%20classifier%20subsets&pg=PA499#v=onepage&q&f=false)\" Data Mining and Knowledge Discovery Series, CRC Press (2015): pp. 498-500.\n",
    "- [2] Wolpert, David H. \"[Stacked generalization.](http://www.sciencedirect.com/science/article/pii/S0893608005800231)\" Neural networks 5.2 (1992): 241-259."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 1 - Simple Stacking CV Classification"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {
    "collapsed": true
   },
   "outputs": [],
   "source": [
    "from sklearn import datasets\n",
    "\n",
    "iris = datasets.load_iris()\n",
    "X, y = iris.data[:, 1:3], iris.target"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3-fold cross validation:\n",
      "\n",
      "Accuracy: 0.91 (+/- 0.01) [KNN]\n",
      "Accuracy: 0.90 (+/- 0.03) [Random Forest]\n",
      "Accuracy: 0.92 (+/- 0.03) [Naive Bayes]\n",
      "Accuracy: 0.93 (+/- 0.02) [StackingClassifier]\n"
     ]
    }
   ],
   "source": [
    "from sklearn import model_selection\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.naive_bayes import GaussianNB \n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from mlxtend.classifier import StackingCVClassifier\n",
    "import numpy as np\n",
    "\n",
    "RANDOM_SEED = 42\n",
    "\n",
    "clf1 = KNeighborsClassifier(n_neighbors=1)\n",
    "clf2 = RandomForestClassifier(random_state=RANDOM_SEED)\n",
    "clf3 = GaussianNB()\n",
    "lr = LogisticRegression()\n",
    "\n",
    "# The StackingCVClassifier uses scikit-learn's check_cv\n",
    "# internally, which doesn't support a random seed. Thus\n",
    "# NumPy's random seed need to be specified explicitely for\n",
    "# deterministic behavior\n",
    "np.random.seed(RANDOM_SEED)\n",
    "sclf = StackingCVClassifier(classifiers=[clf1, clf2, clf3], \n",
    "                            meta_classifier=lr)\n",
    "\n",
    "print('3-fold cross validation:\\n')\n",
    "\n",
    "for clf, label in zip([clf1, clf2, clf3, sclf], \n",
    "                      ['KNN', \n",
    "                       'Random Forest', \n",
    "                       'Naive Bayes',\n",
    "                       'StackingClassifier']):\n",
    "\n",
    "    scores = model_selection.cross_val_score(clf, X, y, \n",
    "                                              cv=3, scoring='accuracy')\n",
    "    print(\"Accuracy: %0.2f (+/- %0.2f) [%s]\" \n",
    "          % (scores.mean(), scores.std(), label))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAk0AAAHiCAYAAAD1WPj+AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJzs3Xl8VPW9//HXJxskQCAKsgvWBbVaN7QuV2rrghuKt2j1\nWrG3rVhtbavyK21ttXr1tr2lWlurhWpVXFupuPWq1avW2rqhRXEBQUQIhCVIyJ5MZr6/P85JmCQz\nyZlkJrO9n48HD5IzZ77zndF58znf8z3fY845RERERKRnBenugIiIiEg2UNEkIiIiEoCKJhEREZEA\nVDSJiIiIBKCiSURERCQAFU0iIiIiAahoEhGRrGZmx5lZZbr7IblPRZP0yMzWmtkJUb+fa2bbzexz\nZubM7H+77H+vmf3E//k4f59bu+zzkpl9ZSD6LyLp4WdHk5nVm9kmM7vLzIamu1/95Wdag/++6s2s\nZoBfXwViGqloksDM7ELgt8BpwMf+5s+a2dE9PK0BuMDMJqe2dyKSgWY454YCBwOHAD9Ic3+S5SDn\n3FD/z4hEn2xmRanolKSeiiYJxMwuBn4JTHfO/TPqof8BbujhqTXAXcA1qeudiGQy59wm4Gm84gkA\nMzvNzP5lZrVmtr59hNp/bLI/onOhma0zs2ozuyrq8VJ/5Gq7mb0HHB79ema2n5m9YGY1ZvaumZ0R\n9dhdZnarmT3pjxT9w8zGmNmv/PZWmNkhfXmfZnaRma02s0/M7DEzGxf1mDOzb5rZKmCVv21fM3vG\n33+lmZ0Ttf+pZvaemdWZ2QYzm2tmQ4AngXFRI13junVEUkZFkwRxCXAdcLxzbmmXx24F9ok+hRfD\nDcAXzWxKqjooIpnLzCYApwCrozY3ALOBEXij15eY2cwuT/03YApwPHC1me3nb78G2NP/Mx24MOq1\nioHHgb8CuwGXAfd1yZ9zgB8BI4EW4GXgTf/3xcCNfXiPXwB+6rc9Fm80/sEuu80EPgvs7xdAzwD3\n+/08F7jVzPb3970DuNg5Nww4AHjOOdeA9zlujBrp2phoX6XvVDRJECcCrwDLYzzWhFcUXR/vyf5R\n5u/wCi8RyR+PmFkdsB7YQtSIs3PuBefccudcxDn3NvAA8Lkuz7/WOdfknHsLeAs4yN9+DnCDc+4T\n59x64NdRzzkSGAr8zDnX6px7DngCOC9qnyXOuTecc83AEqDZObfIORcG/oh3KrEnb/qjWDVm1v7a\n5wN/cM696ZxrwTsVeVSXqQk/9fvcBJwOrHXO3emca3PO/Qv4M3C2v28Ir7gqd85td8692UufZACo\naJIgLgH2AW43M4vx+O3AaDOb0UMbPwemm9lBPewjIrllpj9SchywL95IDgBm9lkze97MtprZDuAb\n0Y/7NkX93IhXDAGMwyvE2n0c9fM4YL1zLtLl8fFRv2+O+rkpxu+9TVg/1Dk3wv/z7ajX7eiHc64e\n2NbldaP7PAlvTmh78VWDV3iN8R//InAq8LGZ/c3MjuqlTzIAVDRJEJvxhsePxTsd14lzrhW4Fvgv\nIFZRhXNuG/Arfx8RySPOub/hzW2cH7X5fuAxYKJzbjjeaHTM/IihCpgY9fvuUT9vBCaaWUGXxzck\n2O1EbcQrhADwT7/t2uV1XdTP64G/RRVfI/zTbZcAOOded86diXfq7hHgTzHakAGmokkC8c+bHw+c\nbGY3xdjlHmAwcHIPzdwIHA3s18M+IpKbfgWcGDXaPAz4xDnXbGZHAP+RQFt/An5gZhX+fKnLoh57\nFW9U6ntmVmxmxwEz6D6/KNkeAP7TzA42s0HAfwOvOufWxtn/Cbz5oBf4/Sw2s8P9SewlZna+mQ13\nzoWAWqB95GwzsKuZDU/x+5EYVDRJYM65dcAXgFl4Ex6jHwsDVwO79PD8Wryr7eLuIyK5yTm3FViE\nlxMAlwLX+XOermbnSEoQ1+KdCvsIb8L3PVGv04pXJJ0CVOONjs92zq3o73voiXPuWeDHePOSqvAm\nqZ/bw/51wEn+PhvxTkX+HBjk73IBsNbMavFOXZ7vP28FXoG2xj+tp6vnBpA5p5E+ERERkd5opElE\nREQkABVNIiIiIgGoaBIREREJQEWTiIiISAAqmkREREQCSMmdll+seVGX5InkkWkjpgVdlDDjKb9E\n8k/QDNNIk4iIiEgAKppEREREAlDRJCIiIhKAiiYRERGRAFIyEVxE+iACxa3FFEYKscA3ex84Dke4\nIEyoJKTDLRHpLMPzC5KTYSqaRDJEcWsxQ0qGYMWGWeaFjnMOF3I0tDYQGhxKd3dEJINken5BcjJM\nx4siGaIwUpjRgWNmWLFRGClMd1dEJMNken5BcjJMRZNIhjAyO3DAD50MHXoXkfTJhvyC/meYiiYR\n6fDSMy8x45AZnHrQqdz+y9vT3R0RkYSkOsNUNIkIAOFwmBuuvIFbH76VR19/lCcXP8mHKz5Md7dE\nRAIZiAzTRHCRLHTRtHNprP6k2/aykbvw+xcf7FOby5cuZ/dP7c7EPSYCcMoXT+H5J55nz3337Fdf\nRUSipSK/YGAyTEWTSBZqrP6EV0ZWdNt+ZIwgCmpL1RbGjB/T8fvo8aN5e+nbfW5PRCSWVOQXDEyG\n6fSciIiISAAqmkQEgN3G7samDZs6ft+8YTOjx45OY49ERIIbiAxT0SQiABxw2AF8/OHHVK6tJNQa\n4sk/P8lxpx2X7m6JiAQyEBmmOU0iAkBRURE/nP9DvjHzG4QjYc664Cz22m+vdHdLRCSQgcgwFU0i\nWahs5C4xJ02WjdylX+1Omz6NadOn9asNEZGepCq/IPUZ1mvRZGZTgD9GbfoUcLVz7lcp65WI9Kg/\nl+XmE+WXSObJ5vzqtWhyzq0EDgYws0JgA7Akxf0SEek35ZeIJFOiE8GPBz50zn2cis6IiKSQ8ktE\n+iXRoulc4IFUdEREJMWUXyLSL4GLJjMrAc4AHorz+BwzW2pmSx+767Fk9U968MqzrzB39lzOP+58\n5s6eyyvPvpLuLolkJOVXZlKGSbZJ5Oq5U4A3nXObYz3onFsILAR4seZFl4S+SQ9eefYVFty2gAlz\nJnDwlIOpXVnLgtsWAHDkCUemuXciGUf5lWGUYZKNEjk9dx4a2s4YixctZsKcCYz49AgKigoY8ekR\nTJgzgcWLFqe7a5LFfnzJj/ncHp/jrCPOSndXkk35lWGUYZJsA5FfgYomMxsCnAg8nLKeSEKq1lVR\nPqW807byKeVsWrcpzjNEenfm+Wdy25Lb0t2NpFJ+ZSZlmCTbQORXoKLJOdfgnNvVObcjpb2RwMbu\nPpbalbWdttWurGXM7mPiPENy0fbq7Vz6xSuo2VaTlPam/ttUhlcMT0pbmUL5lZmUYZKN+aV7z2Wp\nWbNnUbmwkpp3a4i0Rah5t4bKhZXMmj0r7nPyZdJlvrxPgMV3Pc7ypc08dKcmL0t2STTD8uV7nS/v\nE7Izv3QblSzVPlFy8aLFfLTuI8bsPoaLL7k47gTKfJl0mS/vE7yjtMfu/we7jr6Vx+6/lLP/8wxG\n7Doi3d0SCSSRDMuX73W+vE/I3vxS0ZTFjjzhyMBfpOhJl4D39xxve7yQWrxoMVXrqhi7+1hmzZ6V\nFV/aRN9nNlt81+O0tc2gbOg+1NbM4KE7H+OiubPT3S2RwIJmWF++19mYYcqvzM8vnZ7LE4lMumw/\n2ik9v5SD7zqY0vNLWXDbgqwYJs6XyaXtR2llQ88HoGzo+Tx2/z+SNjdAJJMk+r3O1gxTfmV+fqlo\nyhOJTLrM5kuB82VyaftRWlHRSACKikbS1jaj33MDvvef3+PLx3+ZtavWcvyU43n4bl1wJumX6Pc6\nWzNM+ZX5+aXTc3li1uxZ3rnxOd6RS+3KWioXVnLxJRd327dqXRUHTzm407byKeV8tO6jgepunyXy\nPrPZK88vJdy2je3VD3TZvmu/hrj/587/6W/XRJIu0e91tmaY8ivz80tFU55IZNJl+9FO+3l1yJ6j\nnUQnyGerO/7y63R3QWTAJPq9ztYMU35lPhVNeSTopMtcOdpx6G4YIrkikQtfciHDlF+ZSUWTdJPN\nRzv5dMmuiMSWrRmm/Mp85lzyq1nd8FLSZe7suZSeX9ppWL7m3Rqa7mti/qL5aexZ70obSyktL013\nN3rVVNtEU1lTp23TRkyzNHUn6ZRfki7Kr4HRnwzTSJNk5Xom8WTrBFAR6RvllwwkLTmQ57J1PZN4\n8uWSXRFRfsnAU9GU57J1PZN4+nJPPtlpU+UmvnrqVzlz6pnMPHwm9956b7q7JBKX8kuiDUR+6fRc\nnsu14eBsnQCaKQqLCpn733PZ/+D9aahr4EvHfomjvnAUe+67Z7q7JtKN8kuiDUR+qWjKc9m6nklP\nErk0OZv9/Zm/c89d97Dx442MmzSOC75yAceeeGy/2hw1ZhSjxowCYMiwIewxZQ82b9ysokkykvIr\ne2VrfqloyiOxJkzGW8/kuKOOY+7suRkxuTKXJnomy9+f+Ts3/vZGJl40kUP2PYQdK3Zw429vBOh3\n8LTb8PEGVry9gs9M/UxS2hPpj0Ty6+JLLs6Y3MiUfmSSbM6vQHOazGyEmS02sxVm9r6ZHZXUXkjK\nxZswCXDxJRfTdF8Tb33lLZrua+K4o47jhZdfyIjJlbk20TNZ7rnrHiZeNJGKAyooKCqg4oAKJl40\nkXvuuicp7TfWN3L5ly9n3s/mMbR8aFLaTBflV/ZLJL/aF7DMhNxQfsWWzfkVdKTpZuAp59wsMysB\nypLaC0m56AmTgPf3HG/7/EXzOx35zJ09N+6+8Y6Q4h1N9fcoq6d+5/PR2saPN3LIvod02jZ83+Gs\n+XhNv9sOhUJc/uXLOe2c0zjhzBP63V4GUH5luUTyCxLPMOXXwMrm/Oq1aDKz4cA04CsAzrlWoDXp\nPZGUSmTCZKKTK+OtYrty2UpeePmFfq1um2sTPZNl3KRx7Fixg4oDKjq27Vixg3GTxvWrXecc13zz\nGj415VNceNmF/e1m2im/ckOiOZDI/sqvgZfN+RXk9NwewFbgTjP7l5ndbmZDUtIbSZlE1v9IdK2Q\neJf9LnlwSb8vB9a6JbFd8JULWP/79Wx/ZzuRtgjb39nO+t+v54KvXNCvdv/18r94/IHHefVvrzLr\n6FnMOnoWLz79YpJ6nRbKrxyQaA4ksr/ya+Blc34FOT1XBBwKXOace9XMbga+D/w4eiczmwPMAbjy\npis54ytnJLWj0j+J3MBy1uxZ3HzjzQw7exjFE4oJVYaoe6iO71zxnZhtV62rYlz1OF698lWaNjRR\nOr6USWdMorGukfIp5Z32TfQoKxduvJkK7ZMl77nrHtZ8vIZxk8ZxxTev6PckykOPPpTldcuT0cVM\nofzKAYnmQCIZpvwaeNmcX0GKpkqg0jn3qv/7YrzQ6cQ5txBYCLp3UyZKdP2PSFOE6sXVtO5opWR4\nCcVNxXHbLi0t5YP7PmD8N8czZJ8hNHzQwAe//YDiwuJ+Xw6sdUviO/bEY5N2pUkOU37lgL7kQNAM\nU36lR7bmV69Fk3Nuk5mtN7MpzrmVwPHAe6nvmrRL1iWrQdf/WLxoMYOmDGLHaztoa2ijrbGN3Y7Y\nLe7kRSs0Rp41ktI9SrFCo3SPUkZ+cSTN9zRTubCy30dZ+bJuiSSf8iszJCPDEsmBRDJM+SWJCHr1\n3GXAff6VJ2uA/0xdlyRavEmKEHwyYqJWLl1JW0UbE6+ayJADhtDwTgOVN1ayffv2mPs31jey9xF7\ns+OTHbS0tlBcUszEIyay+p7VXHzJxTrKknRTfqVRpmeY8ksSEahocs4tA6amuC8SQzouWW11rex+\n+e4MO2QYAMMOGcaEyyew7sfrYu4/dvexhDeGmfDpCR3bat6tYczuY3SUlQCHwzmHmaW7K3E553Bk\n19kr5Vd6ZXqGKb+SIxvyC/qfYVoRPMOl45LVSDhC6ZRSXMRhBYaLOEqnlBIJR2Lu39Oky7vn382S\nB5fQWNdI2bAyzjr3LC6cm/2XsqdCuCCMCzkoJiODxzmHCznCBeF0d0WySKZnmPIrOTI9vyA5Gaai\nKcOl495KQ8qH0LyymSEHDCHswhRYgfd7efwrtWNNuvy/P/8f/1z+Tyb+YCLDDhxG3fI6/nTTnwAU\nPDGESkI0tDZQ2FyIkXmh4/DCJlQSSndXJItkQ4Ypv/ov0/MLkpNh5lzyh9p19UnyRM8HiDUZMZHJ\nlfGOmrpO0qwYUuGFxeU7w2L9Tes5Z+Y5TDl4SrfXXLxoMaXnl3YKxZp3a3jtu6+xx/V7UH7ozst2\na9+sZdNPN/H9n31f92PKIdNGTMvMlOwD5VdyJSvDgubXrNmzWLlsJX965E+BMqxmaw1jvztW+ZXn\ngmaYRpoyXLxLVoGEJlfePf9uL0S6HDVVfljJ6i2rO7WzeuFqjj7waF7/6eusqVtD2bAyzjnXC5tY\nr1m7tpYjp3R+zfIp5bSF2hh24LBO24cdOIwPd3w44BNDRSQ9kpFhieTXgtsWcPElF3MO57Dkp0t6\nzbD3f/I+FdUVnV5P+SXxaKQpS82dPTfm6E7TfU3MXzS/2/4zp85kzA/GdDtq+uhHH3HEr44I1E68\n11x+1XIOvOHAwEdqibymZAeNNEmiEsmwZORXvNdc+cxKqhdXc8yCYzq1ofzKL0EzLMhtVCQDVa2r\nirla7aZ1m2Lu31jXGPOoqS3UFrOdlUtXMnPqTE6achIzp87k7vl3U7WuisJxhVSureSjDz6icm0l\nheMKKSosonJhJTXv1hBpi1Dzbg2VCyuZdtw01t+0nto3a3EhR+2btay/aT2DigYl1HcRyT2JZFii\n+bVp3Sbunn93oAzb9dO70vBRg/JLAtHpuSyV6OTKsmFl1C2v63TUVLe8jqLiom7tLP/ZctqGtzF+\n7vhOQ+GloVLWv7aeEUeNoGxwGeHmMOtfXk/FqAq+dsnXYq5ncvf8u7sNkS9/e/mATwwVkcySSIYl\nkl+1K2sJt4Zjns6LlWFbXt7CyN1G0nRfk/JLeqXTc1mqp8mVPc5p6jIx8ugDj+6YE9DezivfeYU9\nrt+DEYdFhcKbtaz+3mpKJ5cy6j9GUTKxhNb1rWy9fyujS0Zz+5O3J9T3m264iVBZaOfVKo3FXH7V\n5ZoTkKV0ek4SlUiGJZJflQsr2bp6KxOuntDt1FoyMkz5lZs0ETzHJXpPo/ZLZLseNUVffdLeDhEY\n/pnhnZ4/7MBhhNvCREIRtv55K2072igaXkQkFGF7TeyVwntSUFrAyFkjO62LIiL5I5EMSyS/Lr7k\nYq7+5tUxT+clK8OUX/lLI03STbxJl6t/sJq9frpXzEtwH1n6SOD2E53ELplPI02SSVKZYcqv3KSR\npjzW35tjnnXuWfzppu5D4YNLBlNUUUS4KUzh4ELCzWGKKoooKixK6DXTsUKwiGSHZNzcN9EMc2HH\n3NlzlV/SKxVNOSYZN8eMNxS+/O3lUAPhknDHjS0H1wymYlRFQq+ZjhWCRSTzJevmvolkmG0y2mij\n9PxS5Zf0SksO5Jjom2MWFBUw4tMjmDBnAosXLe5327Nmz2LbfdsY2jCUSZ+axNCGoWy7bxsu7BJ6\nzVmzZ8VcomDW7Fn97qOIZK9U5hfEzrCPb/uYyedNVn5JIBppyjHJGDqOt/ruOZzDxZdc3G3S5W+u\n/03MdUvivWaik9hFJD8k69RXIhlW3FjM5HMmB35N5Vd+U9GUY5IxdLzkwSVM/MHEjsmS5YeWM/Hy\niSz56RIemftIt3BYvGhxwq955AlHKmREpJNknfpKJMPmzp6r/JLAVDTlmFmzZ/Hzq39OqCxEW20b\nReVFFDcWM++6eXFveNlVY10j4YYwH3zvA1qrWikZW8KoGaNorGuMOUlz1uxZ3HzjzQw7e1inS3C/\nc8V30vAJiEi2SkZ+QWIZduBnDuSpm59SfkkggYomM1sL1AFhoM05NzWVnZK+W7lsJS20sNuXRjNo\nYgkt61vZcudmHrzlQVZVr+o2XA10C57iwmKq7q9i/LfGU7ZPGY0fNLLhlg0UUBBzkuZxRx1HpClC\n9eLqnYu9NRWn4+2LdKP8yh7JyC9ILMOeuvkpmrY00bK4RfklvQq0TpMfOlOdc9VBGtU6Jzsl4/LZ\nRI6wZk6dyegfjGbwfmWYleBcK83vN/LhDz4MvD7J+cedT/F5xZQfU07BoAIiLRFq/1HL5l9vZuqN\nUwPfsFfrluSPTF6nSfnVd8nILwieYcnIL0gsw+LdsFf5lV+0TlMGSMbls/EmNELsI6zGukbK9h1C\nhALAgELK9h2CC7uYK+SuqVsDwGuv79ze0gJjPz2W+g01OBfBrICRnx7LxraNMSd8N9Y1JjQRXEQy\nX7Iu/08kw/qaX+3acyyRDCueUEzrjtZO25RfEk/QJQcc8KyZvWFmc1LZoVySjMtnlzy4hImXexMa\nrdh2Tmh8cEnM/UuHllK7vBYzrx42K/J+LzTqlnde6r9ueR1lw8o6gmZK8zSmNE9jwqj9KN48hCKG\nUFzwae/vzUMoKS1jy1u1NDbS8WfLW7WUDSujdmVtp7ZTvW7Ja693LvREeqD86oNkXf6fSIb1Jb/a\nvfZ6zxkW2WIxMyxUGaJkeEmntrXuksQTdKTp35xzG8xsN+AZM1vhnHsxegc/jOYAXHnTlZzxlTOS\n3NXsk4zLZxvrGr0jLAeh1hDFJcUxj7Da7bn7Prx30zuM+VqEQZNKaPm4lU13VDFy6G6s++U6Rn91\nNIN2H0TLuhY2/2Ezxxz7pW5tnHH85fz6pgsZft4IBk9uoXltE5UPrGXGsd/lnwv/SNFXB9FUXktp\nbTkb7qzk8GPP4t3fvMD4i2Do3uXUr6qlepF3482+CFoMTWmexmuvv9j7jr044vB+NyGZTfnVB8m6\n/D+RDEs0v750bvf8gtgZtuOBmpgZ9sn9dRS3FVPzbk23GweLdBWoaHLObfD/3mJmS4AjgBe77LMQ\nWAiaE9Aukctn4xUKJaVl7Hi7jiEHDiESgVAoTMPyBkpKd44QRf+jX1PdQKQhwtYHNxOuD1M4tJBI\nQwSKixjEILb+cWvHVSmDGMTxp03hiMO911852PtPujr0Ko07Ggg9DOH6LRQOLSG0owGbHOHCveZz\n+y8uZ8vWDew2ajxfP+8WDjv0FN5480keu+MmVmxZybAxI/j89IspGH5kn0eDpjRPS+p+8awc/KJG\nrJJg2onp7kF8iebX/GdedPp/AoaMGMuWt2op3y8qv96vpWzEmIS+M0EyrN3G9Q1EmoPn15SDp3R6\nfm8ZNn7clG4ZdsaF3lVyL922mPc3f8Quo8f0O79AB2O5qteiycyGAAXOuTr/55OA61Lesxwwa/Ys\nbw7AHHo8gmkfVo5lxrHf5bEb5zPm22MZsn8FtW9vZ9Ovqzjj2LlMaZ7WERLtdtmjjPE/OiLmZO3x\nM8ezdelW6urqKKsoY9SJo1i8aDFHnnBkpy/4r//fXexywjgaVzcSqW+lsKyYYSeM45nFd3HKj74F\n9eMYO/RRwvWXsPdeRwBw2KGnsPdeh3PTTRdz+UULKS/fFZqT9EGmUH+LLslsfckv/T/hOffz13L3\nwrkUf72M8r0rqF21nS23V3PhSfMT+ox6y7Bo5RPK2PuSxPML6DXDhp4wjvsfvYaf//ilThn2+d2/\nRnn5rnx+968mLb+65rLkjiAjTaOBJWbWvv/9zrmnUtqrHBFk5djejmTOPedq3nn3ZVZc/Q9cZB1W\nUMy+E47h3HOujrl/rCH1oonl1FXXs+Gljez21dGM+dQ4mtc0s+EPG4msLeC11zsHzpb1mygpHcTY\nr4+ldO/daFrVRNXtVTRvauHppxcRDp9OaekU6utP5+mn7+bss68A4OmnF/HBB9s7bRNJM+VXHx12\n6CkAPHbnTXy0ZSmjd5vMhSfN79geVCIZ9snmKsZPPJjGxp3bguQXBMuwls2huBmW7PyKle0afcp+\nvRZNzrk1wEED0Jec1NPKsdETsOOpra1m25YWxg9dQ2HhSMLharZtOYfa2m3e0VAX7acES/bYeaRW\nv6qWwsFFjJ49hsH7DAZKKN3HGD17DJuu39rRl/YvdFlFGWO+Pqbj8t7Bhw6i8OsFVF5XxVPP/4mi\nsgcBKC2dzXPPncP06RcCjueee4KKitt47rlLmD79wpj9ExlIyq/+OezQUxIukrpKJMMmjNqPlpVt\njNhv5/aaldt6zK/2/Hzt9Rd7zDD7WgGb/msrzz33BKWl3tV77Rl29NGnJzW/YmV6+1QAFU7ZTTfs\nTbPehrnbj4oKC0cC+KHjHR3F8pmjZvHhbyqpfb+G4tYhNL3bypaF1QwuHkLB8AIiLeAcRFqgYHgB\nRQUlTGmeRsOOGi6efg07PtlBUWERRRVFhJvC4CDcFPZ+D4UJh2cAsHHjFwHr6MvTTy8iFDqF6upC\nQqFTOvWvtraaa6/9IrW125LzoYlI1kgkw844/nI23L6Omve3eTfDfX8bG25f12N+gZcxd/zIyy8g\nZoYVd2TY6UDnDFuwYF7K80unfXNDStZp0iTK5Fm27AXC4Y3s2HF/l+3jOoaQoz/vfaceyYTWA3ns\njpt4K2pI/eGn/4eGD6oYFB6Ec42YFdPyYYixY/YG4MPHVrPhfePW6//KLmMnY1sgUhKmpbWFgsJi\nbMtgCgpLKGxZwo5Nd9DUNILNmw9nyJBdWLp0V2prQ7S2/obm5jAlJafy3HOXdRyt6bSdSP4KkmHt\n4p0S7C2/nn56EetWtHHr9X/l+PPO7siwUGGY5lALhcXFFGwdTGFBCeHW+9m8+ZaODCstLee995oo\nK7te+SW9SknRpIo6cfEmDs7+5dz4z/EvAOr2eR9KtyH1v/99CWvuXsXIC8YzePeRNK2rpvqeTXx2\n972pra3m2WcfJRS6ireeuoEvX3Al99/7Q8Z8dSxWbhTVDGbbPdu58uv3sPdehzNv3jmMHHkb4fAl\n/PznD/kjTWEaGoooKppEQ8PHlJR4R2vTp8/WaTuRPHbDDY8ltH+sU4JB8ivS+mMvv47/iTeJ/Z65\njPvqeG9pgepyNt6zge9edGe3DDvmmM/zwguD2b5d+SW904rgGaQv57rblwrorVDdumUTbesdG258\nDUcYo5BgpCzgAAAgAElEQVTC5nK2Dt7E008voq7uc7S2TqKu7nNsqtrA10+8hdv/J3ppgZs47NBT\neOihG7tNoly27AUaGlYQDv+egoJyIpFaGhpg2bJ9AeJOHBcRCSKR/IrOmM7Lo8TOsL/9bRENDY3K\nLwlEc5qyXHuh1dslrvPm/YFdKvaC+mcobPwE6p9hl4q9uPTS+Tz77KM0N59KUdEkmptP5dlnH2XM\n6D39y3KXQv049t7rCGprq/1JlLOB9kmUT3DppfMZMWICkyb9k8mTlzJp0j8ZMWICl146P+b+mtsk\nIolINL9qa7ex916HB8qwgoLhDBs2WvklgahoGgD9nUj4yrOvMHf2XM4/7nzmzp7LK8++0unxIIVT\n+9EY7E5bWyWwO3V1n2PBgnnU1X0O54YRCp2Pc+Ud21taTqKysoWWlpM6JnvHmtC5YMG8uJMre5oA\nqgniIpkvGd/TN958kmt+cRLf+H97c80vTuKNN59M6PmJ5ld7XgXJsLq6z1Ff7/2u/JLemHPJX7z7\n8cfRiuBRHnroRh555Almzjw97tBue8HT9RRd9E0zuy6QGW+9p1in6r7//VNYufIdnBtMJNJMQcFg\nzJopKYHWVohEinCuArPtmLVQVDSUgoJ7aW4eyuDB9ey66/cpLy+hurr7jeIbG6spKxtJQ8MnNDWN\noLS0hiFDdunY3tWYMeO44YbHAn0ukh1mzCDQHcKzgfKrs/58T1cOfpEVS1/hqb8sYPxFEzpus7Th\n95V8/cRbAi9nkEh+FRS0seee+9DQ4Ni27We9ZlhNzRYgRHFxacrza+XgF7XkQIaaNmJaoAzTnKYU\nax8OjjWRsLa22luB9vKFTGFazHU8om+aCXh/z6FjJdznn63hwV/cxLnfu4JDS2bE7cdhh51IdfVR\nbN/+AoWFtwGXUFHxecaNe5ONGw9h+/YXAG97SUkZzn2GxsZSYDzNzetoaTmJqVNLOPvsKzr1O/q9\ndJ0g3tOEyZ4+FxHJDEHzq6fv7tsvL2bPy3Zm2NDDvL8fu8ObYxSknUTyq6Li85SVvcknn3yW5uZg\nGab8kqB0ei7F2oeDS0qmdFubJPpSViBqkbadz69aV0X5lPJObRZNLGftqk289vrOpQI+fHRVj/1Y\ntuwF6uvvIhw+COfChMMHUV9/J6tW/avb9qamV2lquhOYCUwDZrJjx20sW/ZCzH739j4T/VxEJDMk\nkl/xrF1VRdHEchob6fgzdO9yNm9ZG7idRPKrffuOHbcRNMOUXxKUiqYUijdxurZ2W5cjlZ2TC7sW\nTu0rfMPOwKlfVcuEUfsydsv+MduIZd68P1BRMZlJk65i8uT9mTTpKioqJjN//l+7bR81am8KCsqA\nezF7FbiXSKSYSy/9Zcx+9/Q+E/1cRCQz9CW/uprSPK1jle9BkWEdf1pWtjF6t8mB20kkvyoqJvOT\nnzxIJFJMkAzbsOED5ZcEVviTn/wk6Y1+8AHJbzQLPfbY71i9eh8GDz4egIKCMlpatuHcW3z44Vus\nXr0PpaUzOrZ9+tNHATCybRIj2ybx9paPKSws5/V7n6dgbAnFFSU0vx9i0++r+NKJ17D09f+L20bQ\nvqxceQd1dSdSULA/H398NsOGnUl19VO0tR0DnI7ZCKAY2E5l5R+pq6vp9prt7yXW+4zVn54+l3j9\nl8w2ZQrXprsPyaL88vQ1v7oqLx3J3x96iJLdSyipGMyOlZ+w4fZ1CWVYIvnV1tbCyy//N/X1pwGn\n9pph7W0MRH5tK/qY8eMD/geQATVp8KRAGaaJ4Cl01VVnsGnTxm7bR470VtAuKflTx72YWlvPiXse\n/Y03n+Sx/7uJzVvWMnq3yZxx/OUdC7QFbSNeX9onO9bUVBEKjaK4eCuhUDPedLfd8AYjI0A1paVF\n7LLL3t1eM94E8fYJk0H7Em9/yXyaCJ57kpVf0P8MSyS/RowYy7ZtHxGJlAAj6S3DNmw4hmHDSiko\n6DzFNxX5pYngmSvoRHAVTWnw0EM38swzMHTozisu6utv5MQTCXwVRm9tBJ2kCbBhw0q+9a3TCIdv\nobDwW5x22tm89tooSkrOo7LyP5kw4W5aW+9jzJi/s2nTsf3qt+QmFU35Ixn51Vs706fP7nN+3XLL\nk/zzn4/zzDNkXIapaMpcunougyVyL6a+tpHI/ZIWLPg+kchZwB5EImfx3HMPUFo6kqqqXxKJjKKq\n6lBGjBjLqlXVlJWt71e/RSS7JSO/emsH6HN+LVjwPcLhMOHwRmWYJJ1GmnJQ++WzhYW9Xz678yjt\nHswOwbl/UVh4AT/+8W+5/vpvYvYwzv07v/nNk4wfv/cAvxPJFhppkmRJRn7dcsuTQITLLjstozJM\nI02ZK+hIk66ey0FPP72IUOgUqqsLCYVO6XQ5bNdVbKOP0mAw7Udr8+d/g0jkXMLhMiKRc1mw4Htp\neS8ikl+SkV8LFnzPf0wZJsml03M5pv1y2NbW39DcHKak5FSee+6yjsXXup62++CDN3DuFeApnPMm\nTDpXTX19KwUF5xKJhCkoOJd33pnJhg2r0n6kJiK5K1n5tWJFIW1tg4H/yrgMi16HL5auI1G97S/J\nMe3EYPsFLprMrBBYCmxwzp3et27lp3iTshOZrB20jfajtIaGIoqKJtHQ8DElJd7R2vTps3n22UcJ\nha7i2WdvYPr0C/niF7/bbTLmunX/j9bW5ThXjNkknPu440jtuuuWJP8DEkkx5VffJSO/graTjPyq\nr78RuIctW07LuAyLdYuraPHuH9rb82TgJDLS9B3gfaC8tx2ls3iTshOZrB20jWXLXqChYQXh8O8p\nKCgnEqmloQGWLdsXgLq6z9HaOqnjppaxJmOGQhuAFpw7FRgK1AONrFo1KImfisiAUn71UTLyK2g7\nycgvgG3b1uPcHTj3ANmWYRpZymyBiiYzmwCcBtwA6BKDBMS7R1Ei9y5KpI158/7AvHnnsNtundc+\nufTS+Vx33UU0N/+coqJJNDefyrPPzuMXv3i42+u2T8SMtX6KSLZRfvVdMvIrkXZ+9KPfcf313+hX\nfrW/XjZmmEaUMl/QieC/Ar6Ht0JYTGY2x8yWmtnSp55amJTO5YJ49yhK5N5F8SZGxmqjfVs4HGLN\nmpP9S29PZ8GCedTVfQ6YjNlgYHLH0Vq8PhcWenf49kJH91eSrKX86qNk5Ff7/kEybMGCef3Or+h2\nlWGSbL0WTWZ2OrDFOfdGT/s55xY656Y656aefPKcpHUwm8W7R1Ei9zraOTHyVJqbw7S2ntpjG0uX\n/pVw+H6qqqYSiWyiqupQwuH7+eCDN2hquhPnTiUcnopzp9LUdCdvvPFMt9f0hrzvZ8eOqR1/wuH7\nO252KZItlF99l4z8im4nSIa99957hEKL+pVfoAyT1Alyeu4Y4AwzOxXvms5yM7vXOffl1HYt+8U7\n2mk/mop1FNR1Ne94EyPjtTF1Khx99GlcdtlpFBcvwrl/57rrHuhYIbfrhMnDDuveb93KRHKI8quP\nkpFfPV2cEqudXXa5mM9+dit/+ctDfc4vUIZJ6iS0uKWZHQfM7e3qEy0O5+ntfkldtd+76KGHbuSR\nR55g5szTWbbsBdasWUFTEx0TI0tLAVrjtlFYWMjbbx9IcfF1hEJX85nPLCccDut+b5Iy2bC4pfIr\nMcnIr7PPvoKrrjojoQxratpGY+MFyi8ZUEEzTEVThum6Gm77xMigN8fcsGGlvwru3ykoGEskUoVz\nx2bEariSu1Q0CcRezRtc4BvzKr8kXYJmWEIrgjvnXtAaJ6nlDWUfR3X1DwmFPt/jUHgs3iq4/0FB\nwVgAP3j+Q6vhSt5TfqVe1/yKvjglSIYpvyTTaUXwDNI+YTIU+jzNzdsZNAjee+89hg37KPANJlet\n+hfwGqHQHV22F6ey6yKS52Ll13PPPUF5eQnhcHWgDFN+SaZT0ZRB2o/S6uufp7DwNurrL6Gi4stM\nnz4k8B24H3hgbZ9eO9HVfUVEosXKr+LizzN1qvJLcodu2JtBli17gfr6uwiHD8K5MOHwQdTX3zkg\nl8lGr8orIpIo5ZfkAxVNGWTevD9QUTGZSZOuYvLk/Zk06SoqKiYzb96dKX3dzqvyxl9vRUQkHuWX\n5AMVTX1QW1vNtdd+Melfzr6sYpuMviS6uq+IZLdUZFhfV+Hub1+UXzKQVDT1QaqGgvuyim1/+xJv\n1V8drYnkrlRkWF9X4e5PX5RfMtA0ETxBid6oMhGJLtKWjL70dHQYdPKmiGSPVGVYXxaZ7G9flF8y\n0DTSlKBMGgpORl90jyYJ7LXX4v+RrJFLGab8koGmkaYE7BwK/hPQPhR8TlJHmwa6L7oFQR7qR5Ez\n44jNSeyIDLRcyzDllww0FU0JSPZQcLy1RYKsOaJh6TzXz9EdFT/5KZm50Z/8SnZfRAaKiqYEeEPB\nGwOvzt2b6AmQ0c+Ptz2VfZE0UOEjAyyZudGf/Ep2X0QGSkI37A1KN7zsXawbW5aX7xp3u2SpXgqj\nnCl8ZszI+Bv2BqX86p3yS3JN0Bv2aqQpTdqHpktLp1Bfv3NIOt52yQJxCqScKYxEfMovyVcqmtIg\n3gTIo48+PWMmaUoAMYokFUiS65Rfks9UNKVBvAmQCxbM08TITKYiSUT5FYuW3ch+M44ItJuKpjSI\nNwFy1apqysrWa2JkplCRJNKN8is2ZUN+6LVoMrPBwIvAIH//xc65a1LdsVzW15Vzg1zGK32kAikn\nKb+Sr69rI+VMhmlUKa8FGWlqAb7gnKs3s2LgJTN70jn3Sor7JlGCXsYrAWjCdj5RfmWIXMowZUX+\n6rVoct6aBPX+r8X+H12SO4BSeb+7vKBRpLyl/MoMyjDJFYHuPWdmhWa2DNgCPOOcezXGPnPMbKmZ\nLX3qqYXJ7mdey6R7RWWFGPdFm3HE5k5/JH8ov9JPGSa5ItBEcOdcGDjYzEYAS8zsAOfcO132WQgs\nBC0Ol0yZdK+ojKWRJOmB8iu9lGGSSxK6es45V2NmzwMnA+/0tr/0n+7P1IXmI0kfKb/SQxkmuSTI\n1XOjgJAfOKXAicDPU94zAfL4/kw9XKGiAkmCUn6lX95mmOSkICNNY4G7zawQbw7Un5xzT6S2W9Ku\nr5f3ZoV8uS+bpJPyK81yOsMk7wS5eu5t4JAB6Ivkgy6FkgojSSXll4gkk1YEl4ERVSypUBIRkWyk\noklSQyNKIiKSY1Q0SXJpRElEcpVuoZL3VDRJ/2iNJBHJJCkubJRv+U1Fk/SNRpREJEMpkyRVVDRJ\ncJqnJCIieUxFk/RMI0oiIiKAiiaJR8WSiIhIJyqaZCedfhPRFVIiEpeKpnynESWRTvQ9EJF4VDTl\nI40oiYiIJExFUz7QWkoiIiL9NvBFU7LnCxxxRHLby1a9fK4qkkRERPonNUVTD/+AJ/sf78fzsQiL\n855VGImIiKROSoqmgfzHO5mv9fhrozNnJGwAC08RERHpneY0RUn+KFjfizAVRiIiIpml16LJzCYC\ni4DRgAMWOuduTnXHcoEKH5H0Un6JSDIFGWlqA650zr1pZsOAN8zsGefceynum4hIfym/RCRpCnrb\nwTlX5Zx70/+5DngfGJ/qjomI9JfyS0SSKaE5TWY2GTgEeDXGY3OAOQALLr2UOSefnITuSS76whVX\nULtjR7ft5cOH89yNN2Zs25LdlF+SDMqv/Ba4aDKzocCfge8652q7Pu6cWwgsBODxx12yOii5p3bH\nDpYOH95t+9QYYZFJbUv2Un5Jsii/8luvp+cAzKwYL3Duc849nNouiYgkj/JLRJKl16LJzAy4A3jf\nOafxQRHJGsovEUmmICNNxwAXAF8ws2X+n1NT3C8RkWRQfolI0vQ6p8k59xJgA9AXyTHxJjVWbt3K\ne9u3d9uejFWtNm/fHrPtyrY2pn7ta922a4JlblN+SV8pvyQWrQguKRNvUuOuW7cyO8b+oSS8Zgji\ntq0JliISlPJLYlHRJN3EO8Kqqq1lbHl54O2V27dDjC+6A0JtbZ22bfL/7no0pSMpEUlUIhm2eft2\nQsCEiopO2xPJL4AWuucXKMNyjYom6SbeEdb4bdsS3h5LCfCWdT5jMtU57gAO6tJOokdShZEIS4uL\nu20fEyPkRCQ3JZJh723fzmy6j+Qkkl8AY5zr92iQ8ivzBVpyQERERCTfaaRJAmuMRBi3enW37U0J\nttMCjHed1w8MAxHgvbVrO22PN7ly1zPPpNh1X4OwFXivpaXb9kRWK0zGqrxa2Vck88TLsOYE2oiV\nX+BlTNf8gtgZlun5lcx2co2KJglsELAxxvYxcfYvKCiIOTQ9CFjfZdvheMOe+xcWdtoeCcWeXlns\nHJviDJHvEWN/I/YweXmM4fRkrMqrlX1FMk+sDIsA42Lsm0h+4bfRNb8gdoZlen4ls51co6Ipz8U6\nmlizdSuf2bq1276tcdpoBabGOMJqiHMevhXvaC2Wxi5HWWFiT67sSemgQd22FYZCLL3jjoTa6apy\n+3ZN9BTJIPFGQxLJsBP8v7tmWDLyCxLPMOVXZlPRlOdiHU2M2bqVB+i+uM0XiH0qrgRYFGP7F4h9\nmeyYrVsp6bKtHJgOTOiyvbWHNmJxwNRwuNv2cEH/p+8VRiI68hLJIPFGQ0Zv3cqDMfaPlWE7gKeB\nrtOvE8kv8LJqWpdtjtjLBSi/speKJummEPgUUNp1+Ng5SuM8Z//Jk7u3s2ZNj68R7VlgPLC0y2uO\niXHevycGLI3RFwWDSP4wYP8Yp79iZVj7Xl0zLJH8Au/gsWt+NTnHXr30tWtflF+ZTUVTGmT6BLsw\nsAawLgVLvPLFAW/FmFzZ/XgpvmP9v7tOsIw3DJ6ozRqaFkmKTM8v8Cdlx5ms3VX7RO2uGZZIfoE3\n0hRrgnhjgu3EovzKHCqa0iAbJtjtR/f1KIzYk74NOCjOUV0sDm9UKVoYeB6Y3GV7rEmR4BVTsUah\nWoj9OSayom758OExtxckMEQer41YEzdFskk25JcB+8fZHivD9gbKYoysxxIrv8A7vdf90DF2hmV6\nfvXUTr5nmIomScimxx7rtm3MGWfQlMBpNAM2dJnsOL6lhckQ9/RfV3uOGpXQxMhYR2kr16+nKhSK\nuQp5rLYTmcypoz+RzNQ1w8accQYGgTMsVn4BjGlp6ZZfkThtJCO/ILERqEQvqFGGxaaiKc/FOppo\nBQ6OsW9PV88dG2N73KMmM8Z3uQw3jHcEF2soO1VHO+FwmLEFBd2O4OIdMevISySzxPtOtgKfibF/\nrAxLRn61i3cqrms7ycqMSAKTu5VfyaGiKc/FOpqY+rWvJTT8HmsCJPi3FQh4NDV+5kzKYtw+IBmX\n2iaLjrxEMku872QiGZaM/ILMzzDlV3KoaJJuNm/fznvbt3ffnoa+iIgkShkmqaKiKQ0yfZg0BMyO\nsz3e/gfFmA8Qb/+YiotjD3nHOHLri1ifeVUkwpQktS+SLzI9vyCxDEtKfkFKMyzeZ56M9ZskMb0W\nTWb2B+B0YItz7oDUdyn3Zfow6YSKioROz+0xalS/r6bZ8NBDwTvYB/FOQz6XQUEvqaEMS65Mzy9I\nLMOSkV+Q2gzr6TSkDKwgI013AbcQe9FnkZQeeaZyTZhsOGKWpLgLZZjEka351d6OMmxg9Vo0Oede\nNLPJqe+KZKtUHnmmck2YbDhilv5ThklPsjW/QBmWDjohKiIiIhJA0iaCm9kcYA7AgksvZc7JJyer\naRlgGvKVfKP8yi3KMEmVpBVNzrmFwEIAHn88sbusSkbRkK/kG+VXblGGSaro9JyIiIhIAEGWHHgA\nOA4YaWaVwDXOufQvbyp5QcPs0l/KMEkX5VfuMZfAjVYD0/C2SH6ZMaP7fSiylfJLJP8EzDCdnhMR\nEREJQEWTiIiISAAqmkREREQCUNEkIiIiEoCKJhEREZEAVDSJiIiIBKCiSURERCQAFU0iIiIiAaho\nEhEREQlARZOIiIhIACqaRERERAJQ0SQiIiISgIomERERkQBUNImIiIgEoKJJREREJAAVTSIiIiIB\nBCqazOxkM1tpZqvN7Pup7pSISLIov0QkWXotmsysEPgtcAqwP3Ceme2f6o6JiPSX8ktEkinISNMR\nwGrn3BrnXCvwIHBmarslIpIUyi8RSZogRdN4YH3U75X+NhGRTKf8EpGkKUpWQ2Y2B5jj/3qvc+6C\nZLWdicxsjnNuYbr7kWp6n7klX95novItvyB//l/Q+8wt6X6fQUaaNgATo36f4G/rxDm30Dk31Tk3\nFdgvSf3LZHN63yUn6H3mlnx5n+2UX/Hly/8Lep+5Ja3vM0jR9Dqwt5ntYWYlwLnAY6ntlohIUii/\nRCRpej0955xrM7NvAU8DhcAfnHPvprxnIiL9pPwSkWQKNKfJOfe/wP8m0G7On1clP94j6H3mmnx5\nnx2UX3HpfeYWvc8BYM65dL6+iIiISFbQbVREREREAkhq0WRmfzCzLWb2TjLbzSRmNtHMnjez98zs\nXTP7Trr7lApmNtjMXjOzt/z3eW26+5RKZlZoZv8ysyfS3ZdUMbO1ZrbczJaZ2dJ09yfTKL9yh/Ir\n92RKfiX19JyZTQPqgUXOuQOS1nAGMbOxwFjn3JtmNgx4A5jpnHsvzV1LKjMzYIhzrt7MioGXgO84\n515Jc9dSwsyuAKYC5c6509Pdn1Qws7XAVOdcdbr7komUX7lD+ZV7MiW/kjrS5Jx7EfgkmW1mGudc\nlXPuTf/nOuB9cnCFYeep938t9v/k5AQ4M5sAnAbcnu6+SPoov3KH8ktSRXOa+sHMJgOHAK+mtyep\n4Q/5LgO2AM8453LyfQK/Ar4HRNLdkRRzwLNm9oa/ArbkMeVXzlB+DSAVTX1kZkOBPwPfdc7Vprs/\nqeCcCzvnDsZbRfkIM8u5UxZmdjqwxTn3Rrr7MgD+zf/veQrwTf90lOQh5VduUH4NPBVNfeCfI/8z\ncJ9z7uF09yfVnHM1wPPAyenuSwocA5zhny9/EPiCmd2b3i6lhnNug//3FmAJcER6eyTpoPzKKcqv\nAaaiKUH+BMM7gPedczemuz+pYmajzGyE/3MpcCKwIr29Sj7n3A+ccxOcc5PxbrHxnHPuy2nuVtKZ\n2RB/4i9mNgQ4CcjZq8QkNuVXblF+DbxkLznwAPAyMMXMKs3sa8lsP0McA1yAV9Ev8/+cmu5OpcBY\n4Hkzexvv/l3POOdy9nLWPDAaeMnM3gJeA/7inHsqzX3KKMqvnKL8yi0Zk19aEVxEREQkAJ2eExER\nEQlARZOIiIhIACqaRERERAJQ0SQiIiISgIomERERkQBUNImIiIgEoKJJREREJAAVTXnMzH5oZroz\ntoikjZndZWbXx3nsd2b244HuU1+ZmTOzvVLU9vlm9teo348xs1VmVm9mM83sSTO7MBWvLTupaMpi\nZrbWzLb4y8q3b/u6mb0Q5PnOuf92zn09Bf16wcya/S/zDjN70cwOTPbriEhqmdm/mdk//e/xJ2b2\nDzM73My+YmYvpfr1nXPfcM79VzLaMs+3zewdM2vwV31/yMwONLPvm9mLMZ4z0sxa22/2a2ZjzewO\nM6syszozW2Fm10ZncKo45+5zzp0Utek64Bbn3FDn3CPOuVOcc3enuh/5TkVT9isEvpPuTsTwLefc\nUGAX4AXgnvR2R0QSYWblwBPAb/C+x+OBa4GWdParH27Gy8pv472ffYBHgNOAe4GjzWyPLs85F1ju\nnHvHzHbBu81OKXCUc24Y3j3thgN7Dsxb6GQS8G5/GzGzoiT0JW+oaMp+vwDmtt+csiszu9nM1ptZ\nrZm9YWbHRj32k/Y7YvtDu9/q8ty3zOzf/Z/3NbNn/KPNlWZ2TpDOOefCeHff3j+q3SPM7GUzq/GP\n2G4xsxL/sd+a2S+79OMxM7vc/3mcmf3ZzLaa2Udm9u0u7S713+tmM8vZG5KKDIB9AJxzDzjnws65\nJufcX4EQ8DvgKH80uQbAzE4zs3/537/1ZvaT6MaiRq1q/Me/0vUFzWyYmT1vZr/2R4Y6Tt2Z2XH+\n6NCV/gh7lZn9Z9RzdzWzx/3Xf93Mrm8fDTOzvYFvAuc5555zzrU45xr90ZufOecqgefw7ssXbTaw\nyP/5CqAO+LJzbq3/2ax3zn3XOfd2jPcS9/Mws8Fmdq+ZbfM/j9fNbLT/2FfMbI0/kvWRmZ0ftb39\n/XwIfAp43P9vMMi8Ef6vR73GV83sfTPbbmZPm9mkqMecmX3TzFYBq7r9l5e4VDRlv6V4Izlz4zz+\nOnAw3pHV/cBDZjY4xn4PAOe1/2Jm++MdyfzFH3p+xn/+bnhHX7f6+/TIL4bOB16J2hwGLgdGAkcB\nxwOX+o/dDZxnZgX+80cCJwD3+9seB97CO+o9HviumU33n3szcLNzrhzvyO9PvfVPROL6AAib2d1m\ndoqZVQA4594HvgG87J8aaj9ga8ArMkbgjd5cYmYzAfx/sJ/EG7UahZdJy6JfzMx2Bf4P+Idz7tsu\n9o1Rx+CN7IwHvgb8tr1fwG/9PowBLvT/tDseqHTOvdbD+72bqKLJzKb4/bzf33QC8LBzLtJDG9Hi\nfh5+34YDE4Fd8T7PJj9rfw2c4o9kHU2XzwnAObcnsA6Y4f836DT6Z2ZnAj8E/h3v8/47XsZHmwl8\nlqgDWumdiqbccDVwmZmN6vqAc+5e59w251ybc+6XwCBgSow2lgAHRx2NnI8XEC3A6cBa59ydfjv/\nAv4MnN1Dn37tH4HWAd/CG9Zv79MbzrlX/LbWAguAz/mPvQbswAs58Aq0F5xzm4HDgVHOueucc63O\nuTXA7/19wDsC3svMRjrn6p1z0YWaiCTAOVcL/Bvg8L5nW/1R39Fx9n/BObfcORfxR14ewP9eA/8B\nPOuPWoX8TIouBsYBfwMecs79qIduhYDr/Db+F6gHpphZIfBF4Bp/BOk9vCKo3a5AVS9veQkw2syO\n9n+fDTzpnNuaQBsdevk8Qn57e/mjeG/4nzdABDjAzEqdc1XOub6cgvsG8FPn3PvOuTbgv+mc7/iP\nf6bTyFwAACAASURBVOKca+pD+3lLRVMOcM69gzf34PtdHzOzuf4Q7Q6/iBmON8LTtY064C/sLEDO\nA+7zf54EfNYfRq7x2zkf74gunm/7R6CleEXXYjP7jN+nfczsCTPbZGa1eF/o6D7dDXzZ//nL7JwP\nNQkY16UfPwTaQ/xreKcUVvjD3af30D8R6YX/j+5XnHMTgAPwiptfxdrXzD7rn1rbamY78P7hbv9e\nTwQ+7OGlTsPLit/10qVtfhHQrhEYijeaUgSsj3os+udtwNieGnbONQIPAbPNzPAyblHULr22Ea2X\nz+Me4GngQTPbaGb/Y2bFzrkG4Ev+vlVm9hcz2zfoa0aZBNwclZOfAIY3QtdufcxnSo9UNOWOa4CL\niPpSmDd/6XvAOUCFX8TswPvyxPIA3qmxo4DBwPP+9vXA35xzI6L+DHXOXdJbp/yjrL8Dq4H2Kz9u\nA1YAe/un0n7YpU/3Amea2UHAfniTNdv78VGXfgxzzp3qv9Yq59x5eKcQf45XqKX8qhaRfOCcWwHc\nhVc8xTp1dj/wGDDROTccrwBq/16vp+fJ0r8HngL+t4/f2a1AGzAhatvEqJ//D5hgZlN7aeduvLw8\nERiGNx2g3bPAWe1TBwKI+3n4I2XXOuf2xzsFdzreyBbOuaedcyfiFWgr8D6bRK0HLu6SlaXOuX9G\n7RPrv6H0QkVTjnDOrQb+iHdlSLtheEGyFSgys6uB8h6a+V+8I5TrgD9Gnbt/AtjHzC4ws2L/z+Fm\ntl+QvvlF2P7svNJjGFAL1PtHUZ2KL39S5ut4R2N/jho+fg2oM7N5ZlZqZoVmdoCZHe6/zpfNbJTf\n7xr/OUHnH4hIFPMu/rjSzCb4v0/EG4F+BdiMV4SURD1lGPCJc67ZzI7AOyXX7j7gBDM7x8yKzJu0\nfXCXl/wWsBJvcnNpIn31Lzh5GPiJmZX5uTI76vFVwK3AA+ZNKC/xJ2Ofa2bRI/R/x8uOhcCDzrnW\nqMduxMvPu9tPc5nZeDO7sX0UvYu4n4eZfd68pQ4K8bIwBETMbLSZnekXji14px/7kmG/A35gZp/2\nX2+4mfU0nUICUtGUW64Doo/SnsY7evsA+BhopochWX/+0sP4E6+jttfhjRKdC2wENuGN5AzqoS+3\nmHdVRz1e8fMj59yT/mNz8QKkDu8o6o8xnn83cCBRSxX4wXg63uTMj4Bq4Ha8U44AJwPv+q95M3Cu\nzteL9Fkd3kThV82sAa9Yege4Eu9Ks3eBTWZW7e9/KXCdmdXhzbPsuBDDObcOONV/7id4k5sPin4x\nf+L3HKASeDTOBSs9+RZeFmzCy40H6Lw8wreBW/AmjNfgnS48i6jRJL8Pi/AOHqNPzeGc+wRvVCjk\nfyZ1eCNYO/BG0ruK+3ngTW1YjFcwvY83n+sevH+Tr8DL2U/w5kD1OqLflXNuCV5GP+hPgXgHOCXR\ndqQ7i32Bgkh6mdk0vNN0k+JcRSMiEpeZ/RwY45zTKtmSNBppkoxjZsV4i9DdroJJRILwTyd+xjxH\n4F0YsiTd/ZLcoqJJMoo/T6oGbxJkzKt0RERiGIY3vaAB75T/L4FH09ojyTk6PSciIiISgEaaRERE\nRAJQ0SQiIiISQErubvxizYs65yeSR6aNmBZvwdSso/wSyT9BM0wjTSIiIiIBqGgSERERCUBFk4iI\niEgAKppEREREAkjJRHAR6YMIFLcWUxgpxMi8edUOR7ggTKgkpMMtEeksw/MLkpNhKppEMkRxazFD\nSoZgxYZZ5oWOcw4XcjS0NhAaHEp3d0Qkg2R6fkFyMkzHiyIZojBSmNGBY2ZYsVEYKUx3V0Qkw2R6\nfkFyMkxFk0iGMDI7cMAPnQwdeheR9MmG/IL+Z5iKJhHp8NIzLzHjkBmcetCp3P7L29PdHRGRhKQ6\nw1Q0iQgA4XCYG668gVsfvpVHX3+UJxc/yYcrPkx3t0REAhmIDFPRJCIALF+6nN0/tTsT95hIcUkx\np3zxFJ5/4vl0d0tEJJCByDBdPSeShS6adi6N1Z902142chd+/+KDfWpzS9UWxowf0/H76PGjeXvp\n233uo4hILKnILxiYDFPRJJKFGqs/4ZWRFd22HxkjiEREMkk255dOz4kIALuN3Y1NGzZ1/L55w2ZG\njx2dxh6JiAQ3EBmmoklEADjgsAP4+MOPqVxbSag1xJN/fpLjTjsu3d0SEQlkIDJMp+dEBICioiJ+\nOP+HfGPmNwhHwpx1wVnstd9e6e6WiEggA5FhKppEpMO06dOYNn1aurshItInqc6wXosmM5sC/DFq\n06eAq51zv0pZr0SkR2Ujd4k5abJs5P9v787jq6ruvY9/1slAEkggMgUIglWKQ50jtXodqrUqCmKL\n1l4rHbRwpfaxerlqHztYHztbqLeDhWoV6nQLFS/aWyhO17YOiIriFEBQSEgIwSQn83DOev44CWbY\nJ9k72Sdn+r5fr7wim5191omer2uv/VtrHRKH1iQu5ZdI4knm/Bqw02StLQVOADDGZADlwNoYt0tE\n+jGUabnpRPklkniSOb+8FoKfC7xnrf0gFo0REYkh5ZeIDInXTtMVwMOxaIiISIwpv0RkSFx3mowx\n2cBcYHWUv19ojNlsjNm87v51frVP+vHiky+yZMESrjz7SpYsWMKLT74Y7yaJJCTlV2JShkmy8TJ7\n7kLgVWvtPqe/tNauAFYAPFf7nPWhbdKPF598keV3L6d4YTEnzDyBYGmQ5XcvB+DUz5wa59aJJBzl\nV4JRhkky8vJ47otoaDthrFm1huKFxYw5ZgyBzABjjhlD8cJi1qxaE++mSRL77rXf5azDzuLSWZfG\nuyl+U34lGGWY+G048stVp8kYMxI4D3g0Zi0RTyp2V1Aws6DHsYKZBVTurozyEyIDu+TKS7h77d3x\nboavlF+JSRkmfhuO/HLVabLWNlprx1pr62LaGnFt0qGTCJYGexwLlgYpOrQoyk+IDKzkX0oYXTg6\n3s3wlfIrMSnDxG/DkV/aey5JzV8wn7IVZdS+VUu4I0ztW7WUrShj/oL5UX8mXYou0+V9AtRU17D4\n8zdSe6A23k0R8cRrhqXL5zpd3ickZ35pG5Uk1VUouWbVGnbt3kXRoUUsunZR1ALKdCm6TJf32WXN\n/Y+zdXMLq+9bx9eXLIh3c0Rc85Jh6fK5Tpf32SUZ80udpiR26mdOdf1B6l50CUS+L4wcjxZSa1at\noWJ3BZMOncT8BfOT4kPr9X0ms5rqGtY99E/GTvwt6x5azGVfncuYsWPi3SwR19xm2GA+18mYYcqv\nxM8vPZ5LE16KLrvudnKvzOWE+08g98pclt+9PCmGidOpuHTN/Y/T0TGHETkfp6NjDqvv0/pCkpq8\nfq6TNcOUX4lPnaY04aXoMpmnAqdLcWnXXVreqCsByBt1Jese+ueQawNu+upNfOncL/H+9vc5d+a5\nPLpSE84k/rx+rpM1w5RfiZ9f6jSlCS9Fl8l8tzOYAvlk1HWXlpk5DoDMzHG+3K397L6f8cyOZ3it\n5jWeKn2Kz335c340V2RIvH6ukzXDlF+Jn1+qaUoTXoouu+52up6rQ/Lc7XgtkE9WLz6zmVDHAWqq\nH+51fGzSFFSKuOX1c52sGab8Svz8Uqcpjbgtupy/YH5kxsbCyN1ZsDRI2YoyFl27aBha6R9L6u6G\nce9f/jPeTRAZVl4mvqRChim/EpM6TdJHMt/tpNuUXRHpK1kzTPmV+Iy1/vdmteGlxMuSBUvIvTK3\nx7B87Vu1ND/YzJ2r7oxjywaW25RLbkFuvJsxoOZgM815zT2OnTnmTBOn5vhO+SXxovwaHkPJMI00\nSVKuZxJNxe4KTph5Qo9jBTML2LV7V5xaJCKxpPyS4aTZc2kuWdcziSZdpuyKiPJLhp86TWkuWdcz\niSZdpuzGSmVZJV+b/TUuKbmEeafM44HfPhDvJolEpfyS7oYjv/R4Ls2l2nBwshaAJoqMzAyW/GgJ\nR59wNI31jXzhjC/wqXM+xeFHHh7vpon0ofyS7oYjv9RpSnPJup5Jf7xMTZaexheNZ3zReABG5o/k\nsJmHsW/vPnWaJCEpv6S74cgvdZrSiFPBZLT1TM7+1NksWbAkIYorU6nQ009/3/h3/nj/H9n7wV4m\nT5vMVV+5ijPOO8O365d/UM67b7zLcSXH+XZNkcHykl+Lrl2UMLmRKO1INMmaXxm33XabrxcE+KDl\nA/8vKkPSVTA59qtjmXbNNMKTwzyz/BlKTimh5JQS3njgDd5b+R45u3P4ZMknefaFZ/ucWzS2iOKP\nFSdEu+PRlljLas8ia0SWq3P/vvHvLP3NUsZ+ZSzTvz6d0KQQG363gSljpzDt8GlDbktTQxPXfv5a\nrv/e9Rx5/JE9/q6jtYOOrI4ex6blTPvBkF80QSi/Eo+X/FpwTWRF6UTIDeWXs3jmFwwtw1yt02SM\nGQPcA3wCsMDXrLUvRDtf65wkHi/rfwxmrZBod1NDvctK5nVLvPKyzsnCKxeS8685FH6i8OCxmjdr\naHmohRUPrhhSO9rb27nusus47dzT+PI3v9zn75NtnSblV/LzmgNez1d+DV2y5BcMzzpNdwHrrbXz\njTHZQJ7Ln5ME4aVg0mtxZbRVbEu3lPLsC88OaXXbVCv09MveD/Zy4pEn9jg2+sjR7Pxg55Cua63l\n+9/4Ph+b+bGogZOElF9JzmsOeDlf+TX8kjm/BlxywBgzGjgTuLezUW3W2tqYtEZixsv6H17XCok2\n7XftI2uHPB1Y65Y4mzxtMnXv1vU4VvduHZOnTR7SdV974TUef/hxXvrfl5h/2nzmnzaf5zY8N6Rr\nxpPyKzV4zQEv5yu/hl8y55ebdZoOA/YD9xljXjPG3GOMGdn7JGPMQmPMZmPM5nX3r/O1kTJ0Xtb/\nmL9gPjvu2kHpxlJ2vrOT0o2l7LhrR9S1Qip2V9BS3cJL//4Sz17xLC/9+0u0VLfQVN9EwcyCHucW\nzCygcndlTNqdTq76ylXs+f0eat6sIdwRpubNGvb8fg9XfeWqIV33pNNOYmv9Vh598VHWPL+GNc+v\n4czzz/Sp1XGh/EoBXnPAS4Ypv4ZfMueXm8dzmcBJwDettS8ZY+4CbgG+2/0ka+0KYAWoJiAReV3/\nI9wcpnpNNW11bWSPziarOXqBX25uLtse3MaUb0xh5MdH0ritkW2/2UZWRtaQpwNr3RJnXbNM/nj/\nH9n5wU4mT5vMjd+40dfZJylC+ZUCBpMDbjNM+TX8kjm/BiwEN8YUAS9aa6d3/vkM4BZr7UXRfkah\n46/hnrK6ZMESqvKqqNpURUdjB5kjM5kwawITmiY4Fi9+/eKvE740zJhPjSEjJ4NQS4jaF2pp+WML\n2WOzKV5Y3Gc6cLqHhpNk2fAymQrBlV+JIZEzTPnlj2TJL4hxIbi1ttIYs8cYM9NaWwqcC7w9qJaK\nZ9GKFMF9MaJXpZtL6SjsYOqtUxn5iZE0vtlI2dIyampqHM9vamhixqwZ1H1YR2tbK1nZWUydNZUd\nf9zBomsX6S5L4kb5FX+JnmHKL/HC7ey5bwIPds482Ql8NXZNku66FykCke8LI8dj9eFts20cesOh\n5J+YD0D+ifkU31DM7u/udjx/0qGTCO0NUXzMR+uO1L5VS9GhRVrd1gOLxVqLMQk5aANEZqdYkm4g\nRvkVR4meYcovfyRDfsHQM8xVp8lauwUoGfSryKDFY8pqOBQmd2YuNmwxAYMNW3Jn5hIOhR3Pn79g\nPnctvYv8y/LJKs6ivayd+tX1XH/j9ay8cyVrH1lLU30Tefl5XHrFpXx5ScpMZfdVKBDCtlvIIiGD\nx1qLbbeEAqF4N8UT5Vd8JXqGKb/8kej5Bf5kmLZRSXDx2FtpZMFIWkpbGPmJkYRsiIAJRP5c0GfS\n0UFORZdP/fkpnt/6PFO/PZX8Y/Op31rPn5b9CUDB46A9u53GtkYyWjIwJF7oWCJh057dHu+mSBJJ\nhgxTfg1doucX+JNhrlYE90qFlP7pXg/QuxgR8FRcGe2uqXeRZuHIwkhY3PBRWOxZtofL513OzBNm\n9nnNNavWOK56u+lbmzjsjsMoOOmjabvBV4NU/riSW35yi/ZjSiGJWgg+GMovf/mVYW7za/6C+ZRu\nKeVPj/3JVYbV7q9l0rcmKb/SnN8rgkucRJuyCngqrlx558pIiPS6ayp7r4wdVTt6XGfHih2cduxp\nvPzjl9lZv5O8/DwuvyISNk6vGXw/yKkze75mwcwCOto7yD82v8fx/GPzea/uvWEvDBWR+PAjw7zk\n1/K7l7Po2kVczuWs/fHaATPsndveobC6sMfrKb8kGo00JSmvexrNK5lH0beL+tw17frOLmb9ctaQ\n9qTbeutWjv3hsa7v1Ly8piQHjTSJV14yzI/8ivaapRtLqV5TzenLT+9xDeVXenGbYW5WBJcEVLG7\nwtNqtU31TY53TR3tHY7XKd1cyrySeXx25meZVzKPlXeupGJ3BRmTMyh7v4xd23ZR9n4ZGZMzyMzI\ndFz19syzz2TPsj0EXw1i2y3BV4PsWbaHEZkjhrzSrogkNy8Z5jW/KndXsvLOla4ybOwxY2nc1aj8\nElf0eC5JeS2uzMvPo35rfY+7pvqt9WRmZfa5ztafbKVjdAdTlkzpMRSe257Lnk17GPOpMeTl5BFq\nCbHnhT0Uji/k6muvdlzPZOWdK/sMkW99Y+uwF4aKSGLxkmFe8itYGiTUFnJ8nOeUYVUvVDFuwjia\nH2xWfsmA9HguSfVXXNlvTVOvwsjTjj3tYE1A13VevP5FDrvjMMac3C0UXg2y46Yd5E7PZfy/jid7\najZte9rY/9B+JmZP5J6/3uOp7ct+uIz2vPaPZqs0ZXHDrTeoJiBJ6fGceOUlw7zkV9mKMvbv2E/x\n94r7PFrzI8OUX6lJheApzuueRl1TZHvfNXWffdJ1HcIw+rjRPX4+/9h8Qh0hwu1h9v95Px11HWSO\nziTcHqam1nml8P4EcgOMmz+ux7ooIpI+vGSYl/xadO0ivveN7zk+zvMrw5Rf6UsjTdJHtKLLHd/e\nwRE/PsJxCu5jmx9zfX2vReyS+DTSJIkklhmm/EpNcR1p2vSyt/NnnRKLVqSvoW6OeekVl/KnZX2H\nwnOyc8gszCTUHDq4sWVmYSaZGZmeXjMeKwSLSHLwY3NfrxlmQ5YlC5Yov2RAMek0zWw509P5m15+\nzvW56mD1z4/NMaMNhW99YyvUQig7dHBjy5zaHArHF3p6zXisECwiic+vzX29ZJipNHTQQe6Vucov\nGVBC1DS57WSV5jznehQrXTtXsdwcc/6C+X0LNx8sIyOU4ek1u67DQhxXCBaR9BTrzX2dMuz1u19n\n+henK7/ElYToNLnlZQTL7ehVqnWu/Bg6jrb67uVczqJrF/UpuvzVHb9yXLck2mt6LWIXkfTg16Mv\nLxmW1ZTF9Munu35N5Vd6S6pOkxduOlhuR66SqWPlx9Dx2kfWMvXbUw8WSxacVMDUG6ay9sdreWzJ\nY33CYc2qNZ5f89TPnKqQEZEe/Hr05SXDlixYovwS11K20+SGXx2rROpUzV8wn59+76e057XTEewg\nsyCTrKYsbr795qgbXvbWVN9EqDHEtpu20VbRRvakbMbPGU9TfZNjkeb8BfO5a+ld5F+W32MK7vU3\nXh+H34CIJCs/8gu8Zdixxx3L+rvWK7/ElbTuNLkxUMcq0TpVpVtKaaWVCV+YyIip2bTuaaPqvn08\n8utH2F69vc9wNdAneLIysqh4qIIp100h7+N5NG1rovzX5QQIOBZpnv2pswk3h6leU/3RYm/NWcP3\npkUkJfiRX+Atw9bftZ7mqmZa17Qqv2RArtZpMsa8D9QDIaDDWlvS3/mPP47WOen032/+jH9sWMOH\n+yo4ZOIk/uX8+RxZ8tGwrpsOlZc7rHkl85j47YnkHJWHMdlY20bLO0289+33XK9PcuXZV5L1xSwK\nTi8gMCJAuDVM8J9B9v3nPkqWlrjesFfrlqSPRF6nyWt+aZ2mj/gx/R/cZ5gf+QXeMizahr3Kr/QS\ni3WaPm2trR5ke9LSK6/+laf+tpIp1xzKx2YcQXB7DU/ds5LitmM5+aQLBxylmnVK9IJGcL7Daqpv\nIu/IkYQJAAbIIO/IkdiQdVwhd2f9TsfXnnTiJGrLawnbMAETYNKJk9jbsdex4LupvslTIbhIHCi/\nPPJr+r+XDPMrv8B9hmUVZ9FW19bjmPJLognEuwGpbN1Ty5hyzaGMOWosgcwAY44ay5RrDmXdU8uA\nyKO/aF8QWSR09QNrmfKtqYw6oQCbYRh1QgFTvjWVtY+sdXzN3FG5BLcGMSbSHzYmM/LnDEP91p5L\n/ddvrScvP6/PNSYdOglTbcgI5JKdeRQZgVxMtSEvP49gabDHucHSYNTjWrdEJHl1n/4fyAww5pgx\nFC8sZs2qNZ6us/aRtUy9IVKUbbLMR0XZDhnmR36BtwxrL2sne3R2j2PKL4nGbafJAk8aY14xxix0\nOsEYs9AYs9kYs3n9+hX+tTCJ7avaRcGMwh7HCmYUsq/q/QF/tqvz1NbUTMExozE2QKitA2MD5H8i\nn8ZgE5teps/X4Yd+nPJl5dT+vYrm3bXU/r2K8mXljBs1gd2/2M3+p/YT3B5k/1P72f2L3Vx6xaV9\nXnv+gvm8/dN3qN8epK11B/Xbg7z903e49IpLKVtRxoE3DlC2vTzyfUXZweO1b9US7ghT+1YtZSvK\nmL9gvl+/SpGh8JRf6+5fN8zNS0wVuyscR5Ard1d6uk5TfVNklMhCe2s72MgoUVN9U59z/cgv8JZh\n9avryWrKUn6JK24fz/2LtbbcGDMB2GiMedda22MhJGvtCmAFqKapy8QJhxHcXsOYo8YePBbcXsPE\nCdNdXyM3L5/6t4KMPHYkYWsJhTpofLORvLyCPkXqpTnPsXdPI6HmMPsf2UeoIUTGqAxCDWFabSYj\nckaw/7/2H5yVMoIRzDxhZp/XbKpvovnDFjr+XEtHQxuZo7Jp/7CFqYdPZdEJi/j1Hb+lck81RVPH\ncd13FnPqZ05l5pMztW6JJCpP+aWapggv0//7KzPIzs2j7o36SIaFob09ROPWRrJz89j0cs+6ztrq\nRsKNPfMr3BiGrEheuckv8JZh138nMktO+SVuuBppstaWd36vAtYCs2LZqFQx99wbKL9nN7XvHIjc\nwbxzgPJ7djP33BtcX2P2GddRtnQPda/VYTuyqHutjrKle5h9xnV9zp3ZciZFkydzyh1nc9ZvL+Gc\nVZ/jrN9ewil3nE17dpBxsyczoiAPYwwjCvIYN3uy41D7H+66n8LPTCaQl4UBAnlZFH5mMn+4636O\nPPFIaCyieMJz0FjEUScdBURqHL6z7DtMnnA43/3ldxU4kjCUX4Mzf8F8VyPIXR2maKUGc874FuVL\nywi+FsR2ZBF8LUj50jLmnPEtZrac2aPDdchheXzyF7M4Z9U5nPfoeZyz6hw++YtZtGQEmTxvMnmF\nkfzKK8xj8jzn/ALvGab8ErcGHGkyxowEAtba+s5//ixwe8xblgJOPulCANbdt4xdVZuZOGE6X/7s\nnQePu3HF5d/jzbde4N3v/RMb3o0JZHFk8elccfn3HM/fV7WL42f0nJJXMKOQltoGql+qZMLVExjx\nscm07myl6t5K9n3Q9y6x8oNKcnJHMOmaSeTOmEDz9mYq7qmgbU8rT6zcSKhjDnmjZtBaO4fH7/8b\nX7rxMgCeWLmRd17p6HFMJJ6UX4PnZuXr7h2maLxkWLQVwRsONLD3n3uZ+LWJFB02mbZdLez9w14C\nZc73/VV7Ksn2mGHKL3HDzeO5icBaY0zX+Q9Za9fHtFUp5OSTLvTUSeotGKzmQFUrU0btJCNjHKFQ\nNQeqLicYPEBBwdg+50d7JBjIyWTClyeQ8/EcIJucjxsmfHkClXfs7xN4OaPzmHx1EQUnROoZsk8Y\ngbk6wN7/V8H6h18md9QjAOSOupL1D1/BnK98Fmst6x9+mbETfsv6hxcz5yufZfQhowf9vkV8ovwa\nAjcrXw+0lp2XDOv+SLCps+Qp+E6QwIgMxl81gREzcoFsRswwjL9qAnvv2Hew49b9MV9eYR5F1xQd\nXKIg56QRZFwToOJHzhl21txTlV/iyoCP56y1O621x3d+HWOt/eFwNEwiNmxYRSh0MRkZ4wA6Q+di\nNmxY6Xh+tEeCOVkjCYwOEG4FayHcCoHRATIDkVkjwWA1P/jB5wkGDzAikEd2YTbhZosJBwg3W7IL\nswm1h2hsnENTE3yw7Us0NUFjY+RO7YmVG2lvvYiq8pG0t13E4/f/7WCbag/UctPl36fuw7rY/8JE\nulF+xY7bzdO9ZNj8BfN571dlVL4SeSTY/FYbVSuqyckaRcbozB75lTE6kxGBPGa2nEljXS2Lzv8o\nYzIzMskszCTUHAILoeYQmYWZtDX3zbBQxxx+edPvlV/iilYET3BbtjxLKLSXurqHeh2fzGWX3djn\n/GiPBB/d8DMat1UwIjQCa5swJovW99qZVDQDiATbtm01bNiwkqnFRxOoaiac0UpbRxOZmdlkV+WT\nFcgjo20tdZX30to8hrrKc8gelcfGtfk01lqaG39Hc6PFZM5h/cP/dvBuTcPekkzcdgbE3VZUbjNs\n08sQGH0qF1wEr977JO9WlbrOr/fW7WD3ux389o6/ce4XL2P6zOlQC6HsEK1trQQysjBVOQQysslo\n/SjDavadSXZuHq8/38GIvDkH8+ux+5Rf4szViuBeafZc4vnlXQt5aff/MO6qmeQcOo6W3dVU/7GU\nTx46m6999Uf8x398npqaWyks/CFfumoxq/9xO5O/NoXmgiC5wQL2/qGcL3/2TmYccQo333w5GRl3\nEwpdy09/upoNG1ayYUOImpq5GDMNaz9g1CF/4pOzqzh19nn85oYfEcj4LdlZi/nNhls17J2CEnlF\ncK+UX4NTmvPcwCf1o78OmJf8+vnPH+WZ3fey/i/LKfraJNoLWsgK5nDg3hrHDDv99E/z7LM5fINK\nCwAAHKxJREFUyq80F4sVwSWJ7a+qpGOPpXzpJiwhDBlktBSwP6eSDRtWUV9/Fm1t06ivP4vKikgH\n6Z6f30DV/nImjJ/CNV9cxsknXcjq1UsJhS4mN3cmDQ2RIfYtW56lsfFdQqHfEwgUEA4HaWuA8s1H\n8l7HDjI6LiczdwaNDXMO3gl2SaTNjkVkaAbzeXYzsuclvzZsWMlll91Ecdux3POzgTPsf/93FY2N\nTf3m16ick/iwUfklWhE8bdx88x84pPAIaNhIRtOH0LCRQwqPYPHiO3nyyf+mpWU2mZnTaGmZzZNP\n/jdFEw+HhslMGrUZGiYz44hZBIPVPP30E+TmLgAgN3cBTz/9BIsX38mYMcVMm/Y806dvZtq05xkz\nppjFi+88eH6OzWd0ziK2/u0tJlUd02PV8+5fIpJeujoe/Y1Uec2vYPAAM444xVWGBQKjyc+f2G9+\nAa7ySxmW+jJuu+023y+6bRv+XzSJBYPV/OQnV3HSSZ9hxAjnZf/788qrf2X5A9/gvx67nZdff4KC\n3HFMnjTD0zXWrfsdr79eREfHuYRCBzBmHOFwNbt2PUhl5Sza208iFPomxlxIONzArl0PUlNzFvv2\nfYzs7DDGvMl7773Ojh0fJyfnXAACgTxaWw9QWnov9fXnkZV1PJWVVzFq1Fza2poPHu99vrWvc8wx\nnyL7w5Hcc8d3OO+4q5mScRQHMj+gfC99vqZM8fwrk2E2LWfaD+LdBr8ov3pym18HMj+I+ll98ckX\n+eVtv2TlXSt54ekXKMgvoPhjxQf/fsqUyGd9XMc0x5/3ml9ZWaW8997rvPvu9AEz7MMPd9HW1sDI\nkXOHlF/jOqY5ZpjyKzm4zTDVNA2D1auX8thjTzBv3sWOxdv9eeXVv7Lyb0uYcs2hFMwoJLi9hvJ7\ndnte7+mWWy6ktPRNrM0hHG4hEMjBmBays6GtDcLhTKwtxJgajGklM3MUgcADtLSMIiengbFjb6Gg\nIJvq6r57njY1VZOXN47Gxg9pbh5Dbm4tI0cecvB4b0VFk/nhD9e5+r043X1qSDzxqKYpdbnNr9Kc\n5xw/m903/i2YWUCwNEjZijJPaz55ya9AoIPDD/84jY2WAwd+MmCG1dZWAe1kZeUqv9KYapoSRNdw\ncGHh3Tz99LWcf/6XD65NEgxWs2zZIm64YYXjmkvQc9NfIPL9msjsuJNPutDVNQBOPvk8qqs/RU3N\ns2Rk3A1cS2Hhp5k8+VX27j2Rmppngcjx7Ow8rD2OpqZcYAotLbtpbf0sJSXZXHbZjY6vGQxWc/PN\nlzNu3EcF4v21p7/fS3dOAbrpZQWRyHDwml+9t0WBnhv/ApHvCyPHA6NPpbGulkd+vozv/J81UTPD\nS34VFn6avLxX+fDDT9LS4i7Dhiu/hlosL/GnmqYY61qjJDt7Zp+1SbpP849moE1/3VwDItN+Gxru\nJxQ6HmtDhELH09BwH9u3v9bneHPzSzQ33wfMA84E5lFXdzdbtjwb9TX7e59efy8D6b1NA6iuQCQW\nvORX989id04b/2ZOLeD97ZGNf99bt4Pyd0y/GeAlv7qO19XdjdsMG878kuSmTlMMRSucDgYP9LpT\niRxz0rXCd4/rdm766/YaECmkLCyczrRptzJ9+tFMm3YrhYXTufPOv/U5Pn78DAKBPOABjHkJeIBw\nOIvFi3/h+Jr9vU+vv5fBUCdKxH+DyS+njlPXKt8ATU2Rr4btQYrHH8mkqqNdZZiX/CosnM5ttz1C\nOJyFmwwrL98W1/yS5KJC8Bhat+53joXT1r5+sCAxN3dOj+LC3gpyx/H31avJPjSb7MIc6ko/pPye\n3XzhvO+z+eWnXF2jv7Z0FTsGAkfzwQeXkZ9/CdXV6+noOB24GGPGAFlADWVl/0V9fW2f14xWIB6t\nPf39XqK134txHdN6fKk4M/ZUCJ56Bptf3T9zU6ZAQX4B6//zGQKTsskqzKblnXYqf1/hKcO85FdH\nRysvvPAjGhouAmYPmGEDFXx7+b0MlF/9FctLfKkQPAHceutcKiv39jk+btxYgsF2srP/dHAvpra2\ny6M+R3/l1b+y7qll7Kt6n4kTpjP33BsOLtDm9hrR2tJV7FhbW0F7+3iysvbT3t5CpNxtApHByDBQ\nTW5uJoccMqPPa0YrEO8qmHTblmjn+03Fmf5TIXjq8SO/uj5rDc83DinDvOTXmDGTOHBgF+FwNjCO\ngTKsvPx08vNzCQR6lvjGIr+iFctL/LnNMHWa4mD16qVs3AijRn0046KhYSnnnYfr2XUDXcNtgThA\neXkp1113EaHQr8nIuI6LLrqMTZvGk539RcrKvkpx8Ura2h6kqOjvVFaeMaR2J6reHSkFmzfqNKUP\nP/JroOucf/6CQefXr3/9V55//nE2biThMkydpsSl2XMJzOt+coO5RvdCx4GuuXz5LYTDlwKHEQ5f\nytNPP0xu7jgqKn5BODyeioqTGDNmEtu3V5OXt2dI7U5U3We5lOY816MmQyEn8hE/8mug6wCDzq/l\ny28iFAoRCu1NqwyT4aGRphTUNX22+/5w0e7WPrpL+yPGnIi1r5GRcRXf/e5vuOOOb2DMo1j7OX71\nq78yZYq3BTVTgR7luaORJvGLH/n161//FQjzzW9elFAZppGmxOU2wzR7LgVt2LCK9vYLqa7OoL39\nwh7TYYPBan7wg88fnOnR/S4Ncui6W7vzzn8jHL6CUCiPcPgKli+/KS7vJd4GmpknIv7yI7+WL7+p\n8++UYeIvPZ5LMV3TYdvafkVLS4js7Nk8/fQ3Dy6+1vux3bZtr2Dti8B6rI0UTFpbTUNDG4HAFYTD\nIQKBK3jzzXmUl2+P+51avPX3GA80CiUyFH7l17vvZtDRkQP8v4TLsIFutpwyZLhu0JRfA3M90mSM\nyTDGvGaMeSKWDUpFve+OBjo+lGt03aU1NmaSmTmNxsbMg3drwWA1Tz7537S333JwU8vPf/5bFBX9\nB0ccsZUjjnidI47YSnb2l4BTsDYLY6ZhbZbu1BxoFCp5KL8Gz4/8cnsdP/KrqOg/GDNmPOHwFQmX\nYb0zwylDetv08sA/58dX12spv/rnZaTpeuAdoGCgE6WnaEXZXoq13V5jy5ZnaWx8l1Do9wQCBYTD\nQRobYcuWIwGorz+LtrZp1NefxYYNKx2LMdvby4FWrJ0NjAIagCa2bx/h428l9WgUKqEpvwbJj/xy\nex0/8gvgwIE9WHsv1j6MMsyd3p223ttVKb8iXHWajDHFwEXADwFNMfAg2h5Fbvcu8nqNm2/+Azff\nfDkTJvRc+2Tx4ju5/fav09LyUzIzp9HSMpsnn7yZn//80T6v21WI6bR+irjjtOeUZuTFh/Jr8PzI\nLy/X+c53fscdd/zbkPKr6/WSNcMSZZRHM4qduX0890vgJiIrhDkyxiw0xmw2xmxev36FL41LBdH2\nKPKyd1G0wkina3QdC4Xa2bnzgs6ptxezfPnN1NefBUzHmBxg+sG7tWhtzsiI7PAdCR3trzQUGgaP\nK+XXIPmRX13nu8mw5ctvHnJ+db9usmWYl8d28WxXOm9TNWCnyRhzMVBlrX2lv/OstSustSXW2pIL\nLljoWwOTWbQ9irzsdfRRYeRsWlpCtLXN7vcamzf/jVDoISoqSgiHK6moOIlQ6CG2bXuF5ub7sHY2\noVAJ1s6mufk+XnllY5/XjAx5P0RdXcnBr1DooYObXcrQ9BdA4i/l1+D5kV/dr+Mmw95++23a21cN\nKb9AGRZr0W4C04Gbx3OnA3ONMbOJzOksMMY8YK39Umyblvyi3e103U053QX1Xs27b2HkB2RnXxj1\nGiUlcNppF/HNb15EVtYqrP0ct9/+8MEVcnuvhHvyyX3bPRxbmchHuoJHQ+AxofwaJD/yq2vGm9sM\nO+SQRXzyk/v5y19WDzq/QBk2nKLlF6Rmhnla3NIYczawxFp7cX/naXG4iIH2S+qta++i1auX8thj\nTzBv3sVs2fIsO3e+S3MzBwsjc3MB2qJeIyMjgzfeOJasrNtpb/8exx23lVAoFNf93sSbZNvWJRkW\nt1R+eeNHfl122Y3ceutcTxnW3HyApqarlF9JrnuGJXp+QYz2nlPoxF7v1XC7CiPdbsxbXl7auQru\n3wkEJhEOV2DtGQmxGq4MTjJ0oNRpEnBezRus6415lV+pJxnyC2K0Iri19tmBAkeGJjKUfTbV1f+X\n9vZP9zsU7iSyCu6/EghMAugMnn+N+/okMnjpXD/gJ+VX7PXOr+6TU9xkmPIr9aRaDadWBE8gXQWT\n7e2fpqWlhhEj4O233yY/f5frDSa3b38N2ER7+729jmfFsukyTLrPpNE6KpJInPLr6aefoKAgm1Co\n2lWGKb9SX7LXcGrD3gSyevVSNmxooKbmWeBu4FoKCz/N+eePjPkO3L2LNyW5xLt+IBkez7ml/Boc\n5ZcMVrzzC7Rhb1LasuVZGhruJxQ6HmtDhELH09Bw37BMk+2+Kq8kHz2+k3hTfslgJdPjO400JZD+\nVrGN5d2TU/Gm7taS33DevWmkSZRf4qfhHn3SSFMMed2o0q3BrGLrR1u8ru4ryUErkEs0sciwwa7C\nPdS2KL9SU7T8ineGqdM0CLEaCh7MKrZDbUu0VX/97hBK/Gj2nfQWiwwb7CrcQ2mL8iv1OW0pE88M\n0+w5j7xuVOmF10Xa/GhLf3eHsS7elOEXbfZdssxckaGLVYYNZpHJobZF+ZV+nGbfDWd+aaTJo0Qa\nCvajLdqjKX0lyp2bDK9UyjDlV/oaTH71fsw3mEd+KgT3IF6FjoneFkkNQym8VCF4ckik3Eiktkhq\n6L36uJPuo+3dzZmDCsH9NthCx2iiFUC6KYz0uy0iyTTtVwbHz9wYSn753RYR6Fv/5PQ1VOo0eeD3\nUHC0Akg3hZEalpZY0qO71ORnbgwlv/xui8hw0eO5OIm2tojWHJFE1DXsHe2xnR7PpRfll6QaPZ5L\ncNEKIBOpSFOki0adpDvll6QrdZriINraIuXl27TmiCQs1TwJKL8kvWmdpjiIVgC5fPnNWnNEkkLv\ntVLOPC/ODZJho/xKYps2xbsFiWvOLFenqdMUB5ECyL3U1T3U4/j27dXk5e3pc3zLlskKHUlIfsxG\nkeSi/EoiDp2kObP2xaEhqWPAQnBjTA7wHDCCSCdrjbX2+/39jAopRdKL2yLK4ab8krShDtLQzJnj\nKsPcjDS1AudYaxuMMVnAP4wxf7XWvjikBoonwWA1y5Yt4oYbVmg2ioh7yq8EoQzzUZTHbOokxd6A\nnSYbGYpq6PxjVueX7sSGWfe1TzTULeKO8itxKMOGQKNICcNVTZMxJgN4BTgC+I219qWYtkp6iOUm\nwSKpTvkVf8owj3p1ktRBShyulhyw1oastScAxcAsY8wnep9jjFlojNlsjNm8fv0Kv9uZ1rT2icjg\nKb/iTxk2gE2ben4R6Sh1fUni8DR7zlpba4x5BrgAeLPX360AVoAKKf300ZoofwK61j65XHdqIh4p\nv+JDGdaLHrUltQE7TcaY8UB7Z+DkAucBP415ywTof1NL1QWI9E/5FX9pm2H9rImkTlLycjPSNAlY\n2VkXEAD+ZK19IrbNki7R1kTR2icirii/4iylM2yAxSLVOUo92rBXRIYsUddpGgzll/RLRdqpycd1\nmkREnHX9D8TlFgQiqUAdpfSlTpOIROdiryr9D0RE0oU6TSLpyuXmneoUiYhEqNMkkorUIRJxx+Vn\npYs+M+lNnSaRZKLOkIg/un2W9HkRt9RpEkkEHu52FfAig6BFJcUH6jSJxJpGh0TiRyNK4iN1mkQG\nQyNDIolNnSWJAXWaRLpTZ0gkOenxmwwDdZoktXmcGQMKWpGkohElGUbqNEny0RRhkfSmrUwkTtRp\nkvjSSJCIDESdJEkQ6jSJfwbRAQIFoCSRQf43HtUs7dl30AC/W+WEJAJ1mqSnIf5PQcEmSW8Y/+f9\neDp2wvp5z8oPSXTqNKUSnwJYwSXpbDj/+/e3AzYxcUbCNGokKUqdpuHmd6j1ojASSU9+f/aH2glT\nFkkqGrDTZIyZCqwCJgIWWGGtvavfH4pxxyCZKUhEhs+g8ksAZZWIEzcjTR3Av1trXzXG5AOvGGM2\nWmvfjvYD+rCJSILwnF8iItEEBjrBWlthrX2185/rgXeAKbFumIjIUCm/RMRPnmqajDHTgROBl2LR\nGEkP59x4I8G6uj7HC0aP5umlSxP22pLclF/iB+VXenPdaTLGjAL+DHzLWht0+PuFwEKA5YsXs/CC\nC3xrpKSWYF0dm0eP7nO8xCEsEunakryUX+IX5Vd6c9VpMsZkEQmcB621jzqdY61dAawA4PHHrV8N\nFBEZCuWXiPhlwJomY4wB7gXesdZqfFBEkobyS0T8NGCnCTgduAo4xxizpfNrdozbJSLiB+WXiPhm\nwMdz1tp/AGYY2iIpJlpRY9n+/bxdU9PnuB8LVeyrqXG8dllHByVXX93nuAosU5vySwZL+SVOtCK4\nxEy0osax+/ezwOH8dh9esx2iXlsFliLilvJLnKjTJMMuJxBg8/TpfY77EQDFhYWO4TJl584hX1tE\nRPmV3tRpkj6iDUtXBINMKihwfbyspgYcAqA5HOb4HTt6HKvs/N57CNrr8HNZTQ0lDsPbLeGw62uI\nSHLzkmH7ampoJ9Jh6c5LfgHspm9+gbcMU34lPnWapI9ow9JTDhzwfNxJNvC66VlmUmIt9wLH97qO\n17u3jHCYzVlZfY4XdXR4uo6IJC8vGfZ2TQ0L6Pv4y0t+ARRZO+RHaMqvxKdOk7jWFA4z2eEOq9nj\ndVqBKbbnUjghIAy8/f77PY5HK64ce8klZNm+y+m0AW+3tvY57mXhHT9W5dXKviKJJ1qGtXi4hlN+\nQSRjeucXOGdYoueXn9dJNeo0iWsjgL0Ox4uinB8IBBzvskYAe3odO4XI+hdHZ2T0OB5udy6vzLKW\nyih3e4c5nG9wvuMrcLgz9GNVXq3sK5J4nDIsDEx2ONdLftF5jd75Bc4Zluj55ed1Uo06TRIzEwsL\n2XzvvX2OF82d62qBsMHKHTGiz7GM9nbHtoiIOFF+iRN1mtKc0xDszv37OW7//j7ntkW5RhtQ4jAs\nvTPK2iJtRH+k19RraDqEc3FlPJTV1GitFJEEEu0RkpcM+0zn994Z5kd+QeJkmPLLH+o0pTmnIdii\n/ft5mL4rAp6Dc1hkA6scjp+D89oiRfv3k93rWAFwPlDc63hbP9dwYoGSUKjP8VBg6PeGGeGwhqtF\nEki0R0gT9+/nEYfznTKsDtgA9C6/9pJfEMmqM3sdszivsaT8Sl7qNMVBshbYWSA3yvF2j7M7ej/5\n77o/q3C4thcGHNdQmfr++7rLEvFBsuYXOGdYVed3LxnWt3Ipond+QaRmyi3lV+JTpykOkqHA7ij6\nbkxocC76NsDxDkWNOMwOgUhwTel1LAQ8A0zvddypKBIinawih+u34vx79LKibsHo0Y7HAx7u9qJd\nw6lwUySZJEN+GeDoKMedMmwGkNc7wzzkF0RGqvrOy3POsETPr/6uk+4Zpk6TeFK5bl2fY0Vz53q6\nhgHKexU7TmltZTrOI1lODh8/3lNhpJeagmh3bn5cQ0Tiq3eGFc2d27fD1A+n/AIoam3tk1/RRpkS\nPb/6u066U6dJfNEc5a4skZXu2UNFe/uQVyEXkeSWjPkFkdXM9dhueKnTlOachmDbgBMczu1v9twZ\nDsejDjUbw5Rea5eEiAx7NzlcJ1ZDxKFQiEmBQJ9h72iPGTRcLZJYon0m24DjHM53yjA/8quLU37h\ncB2/MiPsobhb+eUPdZrSnNPdyNTPfc5x5sn5Ua6RDWyOtq2AyyHoKfPmkeewfUAirU+iOzeRxBLt\nM+klw/zIL0j8DFN++UOdpjhI9B5/O7AgyvFo5x/vMLwd7XxHWVnOd28OITQYTr/zinCYmT5dXyRd\nJHp+gbcM8yW/IKYZFu137sdSBOLNgJ0mY8wfgIuBKmvtJ2LfpNSX6D3+4sJCT7NjDhs/fsizacpX\nr3bfwEFw+p2XXH01TydQ0EtsKMP8lej5Bd4yzI/8gthmmF/F3TJ0bkaa7gd+jfP6hSIxlcxrwkjC\nuB9lmMSB8iv1DNhpstY+Z4yZHvumSLKK5XB9LNeESYbHDDJ0yjDpT7LmFyjD4kE1TTJkyXrHlKzt\nFhH/JHMOJHPbk5VvnSZjzEJgIcDyxYtZeMEFfl1ahpnuXiTdKL9SizJMYsW3TpO1dgWwAoDHH0/O\nlcIE0N2LpB/lV2pRhkmsaL6iiIiIiAtulhx4GDgbGGeMKQO+b62N/0pdkhY0zC5DpQyTeFF+pR5j\nY7Hnjoa3RdLLnDnudzxNdMovkfTjMsP0eE5ERETEBXWaRERERFxQp0lERETEBXWaRERERFxQp0lE\nRETEBXWaRERERFxQp0lERETEBXWaRERERFxQp0lERETEBXWaRERERFxQp0lERETEBXWaRERERFxQ\np0lERETEBXWaRERERFxQp0lERETEBXWaRERERFxw1WkyxlxgjCk1xuwwxtwS60aJiPhF+SUifhmw\n02SMyQB+A1wIHA180RhzdKwbJiIyVMovEfGTm5GmWcAOa+1Oa20b8AhwSWybJSLiC+WXiPjGTadp\nCrCn25/LOo+JiCQ65ZeI+CbTrwsZYxYCCzv/+IC19iq/rp2IjDELrbUr4t2OWNP7TC3p8j69Srf8\ngvT5b0HvM7XE+326GWkqB6Z2+3Nx57EerLUrrLUl1toS4Cif2pfIFg58SkrQ+0wt6fI+uyi/okuX\n/xb0PlNLXN+nm07Ty8AMY8xhxphs4ApgXWybJSLiC+WXiPhmwMdz1toOY8x1wAYgA/iDtfatmLdM\nRGSIlF8i4idXNU3W2v8B/sfDdVP+uSrp8R5B7zPVpMv7PEj5FZXeZ2rR+xwGxlobz9cXERERSQra\nRkVERETEBV87TcaYPxhjqowxb/p53URijJlqjHnGGPO2MeYtY8z18W5TLBhjcowxm4wxr3e+zx/E\nu02xZIzJMMa8Zox5It5tiRVjzPvGmK3GmC3GmM3xbk+iUX6lDuVX6kmU/PL18Zwx5kygAVhlrf2E\nbxdOIMaYScAka+2rxph84BVgnrX27Tg3zVfGGAOMtNY2GGOygH8A11trX4xz02LCGHMjUAIUWGsv\njnd7YsEY8z5QYq2tjndbEpHyK3Uov1JPouSXryNN1trngA/9vGaisdZWWGtf7fzneuAdUnCFYRvR\n0PnHrM6vlCyAM8YUAxcB98S7LRI/yq/UofySWFFN0xAYY6YDJwIvxbclsdE55LsFqAI2WmtT8n0C\nvwRuAsLxbkiMWeBJY8wrnStgSxpTfqUM5dcwUqdpkIwxo4A/A9+y1gbj3Z5YsNaGrLUnEFlFeZYx\nJuUeWRhjLgaqrLWvxLstw+BfOv99Xgh8o/NxlKQh5VdqUH4NP3WaBqHzGfmfgQettY/Guz2xZq2t\nBZ4BLoh3W2LgdGBu5/PyR4BzjDEPxLdJsWGtLe/8XgWsBWbFt0USD8qvlKL8GmbqNHnUWWB4L/CO\ntXZpvNsTK8aY8caYMZ3/nAucB7wb31b5z1r7bWttsbV2OpEtNp621n4pzs3ynTFmZGfhL8aYkcBn\ngZSdJSbOlF+pRfk1/PxecuBh4AVgpjGmzBhztZ/XTxCnA1cR6dFv6fyaHe9GxcAk4BljzBtE9u/a\naK1N2emsaWAi8A9jzOvAJuAv1tr1cW5TQlF+pRTlV2pJmPzSiuAiIiIiLujxnIiIiIgL6jSJiIiI\nuKBOk4iIiIgL6jSJiIiIuKBOk4iIiIgL6jSJiIiIuKBOk4iIiIgL6jSJiIiIuPD/AffVA4N/8XUe\nAAAAAElFTkSuQmCC\n",
      "text/plain": [
       "<matplotlib.figure.Figure at 0x10c5186d8>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "from mlxtend.plotting import plot_decision_regions\n",
    "import matplotlib.gridspec as gridspec\n",
    "import itertools\n",
    "\n",
    "gs = gridspec.GridSpec(2, 2)\n",
    "\n",
    "fig = plt.figure(figsize=(10,8))\n",
    "\n",
    "for clf, lab, grd in zip([clf1, clf2, clf3, sclf], \n",
    "                         ['KNN', \n",
    "                          'Random Forest', \n",
    "                          'Naive Bayes',\n",
    "                          'StackingCVClassifier'],\n",
    "                          itertools.product([0, 1], repeat=2)):\n",
    "\n",
    "    clf.fit(X, y)\n",
    "    ax = plt.subplot(gs[grd[0], grd[1]])\n",
    "    fig = plot_decision_regions(X=X, y=y, clf=clf)\n",
    "    plt.title(lab)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 2 - Using Probabilities as Meta-Features"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Alternatively, the class-probabilities of the first-level classifiers can be used to train the meta-classifier (2nd-level classifier) by setting `use_probas=True`. For example, in a 3-class setting with 2 level-1 classifiers, these classifiers may make the following \"probability\" predictions for 1 training sample:\n",
    "\n",
    "- classifier 1: [0.2, 0.5, 0.3]\n",
    "- classifier 2: [0.3, 0.4, 0.4]\n",
    "\n",
    "This results in *k* features, where *k* = [n_classes * n_classifiers], by stacking these level-1 probabilities:\n",
    "\n",
    "- [0.2, 0.5, 0.3, 0.3, 0.4, 0.4]\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "3-fold cross validation:\n",
      "\n",
      "Accuracy: 0.91 (+/- 0.01) [KNN]\n",
      "Accuracy: 0.91 (+/- 0.06) [Random Forest]\n",
      "Accuracy: 0.92 (+/- 0.03) [Naive Bayes]\n",
      "Accuracy: 0.95 (+/- 0.04) [StackingClassifier]\n"
     ]
    }
   ],
   "source": [
    "clf1 = KNeighborsClassifier(n_neighbors=1)\n",
    "clf2 = RandomForestClassifier(random_state=1)\n",
    "clf3 = GaussianNB()\n",
    "lr = LogisticRegression()\n",
    "\n",
    "# The StackingCVClassifier uses scikit-learn's check_cv\n",
    "# internally, which doesn't support a random seed. Thus\n",
    "# NumPy's random seed need to be specified explicitely for\n",
    "# deterministic behavior\n",
    "np.random.seed(RANDOM_SEED)\n",
    "sclf = StackingCVClassifier(classifiers=[clf1, clf2, clf3],\n",
    "                            use_probas=True,\n",
    "                            meta_classifier=lr)\n",
    "\n",
    "print('3-fold cross validation:\\n')\n",
    "\n",
    "for clf, label in zip([clf1, clf2, clf3, sclf], \n",
    "                      ['KNN', \n",
    "                       'Random Forest', \n",
    "                       'Naive Bayes',\n",
    "                       'StackingClassifier']):\n",
    "\n",
    "    scores = model_selection.cross_val_score(clf, X, y, \n",
    "                                              cv=3, scoring='accuracy')\n",
    "    print(\"Accuracy: %0.2f (+/- %0.2f) [%s]\" \n",
    "          % (scores.mean(), scores.std(), label))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 3 - Stacked CV Classification and GridSearch"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To set up a parameter grid for scikit-learn's `GridSearch`, we simply provide the estimator's names in the parameter grid -- in the special case of the meta-regressor, we append the `'meta-'` prefix."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.673 +/- 0.01 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}\n",
      "0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}\n",
      "0.920 +/- 0.02 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}\n",
      "0.893 +/- 0.02 {'kneighborsclassifier__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}\n",
      "0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}\n",
      "0.667 +/- 0.00 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}\n",
      "0.947 +/- 0.02 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}\n",
      "0.947 +/- 0.02 {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}\n",
      "Best parameters: {'kneighborsclassifier__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}\n",
      "Accuracy: 0.95\n"
     ]
    }
   ],
   "source": [
    "from sklearn.linear_model import LogisticRegression\n",
    "from sklearn.neighbors import KNeighborsClassifier\n",
    "from sklearn.naive_bayes import GaussianNB \n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "from sklearn.model_selection import GridSearchCV\n",
    "from mlxtend.classifier import StackingCVClassifier\n",
    "\n",
    "# Initializing models\n",
    "\n",
    "clf1 = KNeighborsClassifier(n_neighbors=1)\n",
    "clf2 = RandomForestClassifier(random_state=RANDOM_SEED)\n",
    "clf3 = GaussianNB()\n",
    "lr = LogisticRegression()\n",
    "\n",
    "# The StackingCVClassifier uses scikit-learn's check_cv\n",
    "# internally, which doesn't support a random seed. Thus\n",
    "# NumPy's random seed need to be specified explicitely for\n",
    "# deterministic behavior\n",
    "np.random.seed(RANDOM_SEED)\n",
    "sclf = StackingCVClassifier(classifiers=[clf1, clf2, clf3], \n",
    "                            meta_classifier=lr)\n",
    "\n",
    "params = {'kneighborsclassifier__n_neighbors': [1, 5],\n",
    "          'randomforestclassifier__n_estimators': [10, 50],\n",
    "          'meta-logisticregression__C': [0.1, 10.0]}\n",
    "\n",
    "grid = GridSearchCV(estimator=sclf, \n",
    "                    param_grid=params, \n",
    "                    cv=5,\n",
    "                    refit=True)\n",
    "grid.fit(X, y)\n",
    "\n",
    "cv_keys = ('mean_test_score', 'std_test_score', 'params')\n",
    "\n",
    "for r, _ in enumerate(grid.cv_results_['mean_test_score']):\n",
    "    print(\"%0.3f +/- %0.2f %r\"\n",
    "          % (grid.cv_results_[cv_keys[0]][r],\n",
    "             grid.cv_results_[cv_keys[1]][r] / 2.0,\n",
    "             grid.cv_results_[cv_keys[2]][r]))\n",
    "\n",
    "print('Best parameters: %s' % grid.best_params_)\n",
    "print('Accuracy: %.2f' % grid.best_score_)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In case we are planning to use a regression algorithm multiple times, all we need to do is to add an additional number suffix in the parameter grid as shown below:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "0.673 +/- 0.01 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}\n",
      "0.667 +/- 0.00 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}\n",
      "0.920 +/- 0.02 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}\n",
      "0.893 +/- 0.02 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}\n",
      "0.667 +/- 0.00 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}\n",
      "0.667 +/- 0.00 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}\n",
      "0.947 +/- 0.02 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}\n",
      "0.940 +/- 0.02 {'kneighborsclassifier-1__n_neighbors': 1, 'kneighborsclassifier-2__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}\n",
      "0.667 +/- 0.00 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}\n",
      "0.667 +/- 0.00 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}\n",
      "0.953 +/- 0.02 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}\n",
      "0.927 +/- 0.03 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}\n",
      "0.667 +/- 0.00 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 10}\n",
      "0.667 +/- 0.00 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 5, 'meta-logisticregression__C': 0.1, 'randomforestclassifier__n_estimators': 50}\n",
      "0.940 +/- 0.03 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}\n",
      "0.940 +/- 0.03 {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 5, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 50}\n",
      "Best parameters: {'kneighborsclassifier-1__n_neighbors': 5, 'kneighborsclassifier-2__n_neighbors': 1, 'meta-logisticregression__C': 10.0, 'randomforestclassifier__n_estimators': 10}\n",
      "Accuracy: 0.95\n"
     ]
    }
   ],
   "source": [
    "from sklearn.model_selection import GridSearchCV\n",
    "\n",
    "# Initializing models\n",
    "\n",
    "clf1 = KNeighborsClassifier(n_neighbors=1)\n",
    "clf2 = RandomForestClassifier(random_state=RANDOM_SEED)\n",
    "clf3 = GaussianNB()\n",
    "lr = LogisticRegression()\n",
    "\n",
    "# The StackingCVClassifier uses scikit-learn's check_cv\n",
    "# internally, which doesn't support a random seed. Thus\n",
    "# NumPy's random seed need to be specified explicitely for\n",
    "# deterministic behavior\n",
    "np.random.seed(RANDOM_SEED)\n",
    "sclf = StackingCVClassifier(classifiers=[clf1, clf1, clf2, clf3], \n",
    "                            meta_classifier=lr)\n",
    "\n",
    "params = {'kneighborsclassifier-1__n_neighbors': [1, 5],\n",
    "          'kneighborsclassifier-2__n_neighbors': [1, 5],\n",
    "          'randomforestclassifier__n_estimators': [10, 50],\n",
    "          'meta-logisticregression__C': [0.1, 10.0]}\n",
    "\n",
    "grid = GridSearchCV(estimator=sclf, \n",
    "                    param_grid=params, \n",
    "                    cv=5,\n",
    "                    refit=True)\n",
    "grid.fit(X, y)\n",
    "\n",
    "cv_keys = ('mean_test_score', 'std_test_score', 'params')\n",
    "\n",
    "for r, _ in enumerate(grid.cv_results_['mean_test_score']):\n",
    "    print(\"%0.3f +/- %0.2f %r\"\n",
    "          % (grid.cv_results_[cv_keys[0]][r],\n",
    "             grid.cv_results_[cv_keys[1]][r] / 2.0,\n",
    "             grid.cv_results_[cv_keys[2]][r]))\n",
    "\n",
    "print('Best parameters: %s' % grid.best_params_)\n",
    "print('Accuracy: %.2f' % grid.best_score_)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Note**\n",
    "\n",
    "The `StackingCVClassifier` also enables grid search over the `classifiers` argument. However, due to the current implementation of `GridSearchCV` in scikit-learn, it is not possible to search over both, differenct classifiers and classifier parameters at the same time. For instance, while the following parameter dictionary works\n",
    "\n",
    "    params = {'randomforestclassifier__n_estimators': [1, 100],\n",
    "    'classifiers': [(clf1, clf1, clf1), (clf2, clf3)]}\n",
    "    \n",
    "it will use the instance settings of `clf1`, `clf2`, and `clf3` and not overwrite it with the `'n_estimators'` settings from `'randomforestclassifier__n_estimators': [1, 100]`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Example 4 - Stacking of Classifiers that Operate on Different Feature Subsets"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The different level-1 classifiers can be fit to different subsets of features in the training dataset. The following example illustrates how this can be done on a technical level using scikit-learn pipelines and the `ColumnSelector`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StackingCVClassifier(classifiers=[Pipeline(steps=[('columnselector', ColumnSelector(cols=(0, 2))), ('logisticregression', LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,\n",
       "          intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,\n",
       "          penalty='l2', random_state=None, solve...='l2', random_state=None, solver='liblinear', tol=0.0001,\n",
       "          verbose=0, warm_start=False))])],\n",
       "           cv=2,\n",
       "           meta_classifier=LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True,\n",
       "          intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1,\n",
       "          penalty='l2', random_state=None, solver='liblinear', tol=0.0001,\n",
       "          verbose=0, warm_start=False),\n",
       "           shuffle=True, stratify=True, use_features_in_secondary=False,\n",
       "           use_probas=False, verbose=0)"
      ]
     },
     "execution_count": 1,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from sklearn.datasets import load_iris\n",
    "from mlxtend.classifier import StackingCVClassifier\n",
    "from mlxtend.feature_selection import ColumnSelector\n",
    "from sklearn.pipeline import make_pipeline\n",
    "from sklearn.linear_model import LogisticRegression\n",
    "\n",
    "iris = load_iris()\n",
    "X = iris.data\n",
    "y = iris.target\n",
    "\n",
    "pipe1 = make_pipeline(ColumnSelector(cols=(0, 2)),\n",
    "                      LogisticRegression())\n",
    "pipe2 = make_pipeline(ColumnSelector(cols=(1, 2, 3)),\n",
    "                      LogisticRegression())\n",
    "\n",
    "sclf = StackingCVClassifier(classifiers=[pipe1, pipe2], \n",
    "                            meta_classifier=LogisticRegression())\n",
    "\n",
    "sclf.fit(X, y)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# API"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "## StackingCVClassifier\n",
      "\n",
      "*StackingCVClassifier(classifiers, meta_classifier, use_probas=False, cv=2, use_features_in_secondary=False, stratify=True, shuffle=True, verbose=0, store_train_meta_features=False, use_clones=True)*\n",
      "\n",
      "A 'Stacking Cross-Validation' classifier for scikit-learn estimators.\n",
      "\n",
      "New in mlxtend v0.4.3\n",
      "\n",
      "**Notes**\n",
      "\n",
      "The StackingCVClassifier uses scikit-learn's check_cv\n",
      "internally, which doesn't support a random seed. Thus\n",
      "NumPy's random seed need to be specified explicitely for\n",
      "deterministic behavior, for instance, by setting\n",
      "np.random.seed(RANDOM_SEED)\n",
      "prior to fitting the StackingCVClassifier\n",
      "\n",
      "**Parameters**\n",
      "\n",
      "- `classifiers` : array-like, shape = [n_classifiers]\n",
      "\n",
      "    A list of classifiers.\n",
      "    Invoking the `fit` method on the `StackingCVClassifer` will fit clones\n",
      "    of these original classifiers that will\n",
      "    be stored in the class attribute `self.clfs_`.\n",
      "\n",
      "- `meta_classifier` : object\n",
      "\n",
      "    The meta-classifier to be fitted on the ensemble of\n",
      "    classifiers\n",
      "\n",
      "- `use_probas` : bool (default: False)\n",
      "\n",
      "    If True, trains meta-classifier based on predicted probabilities\n",
      "    instead of class labels.\n",
      "\n",
      "- `cv` : int, cross-validation generator or an iterable, optional (default: 2)\n",
      "\n",
      "    Determines the cross-validation splitting strategy.\n",
      "    Possible inputs for cv are:\n",
      "    - None, to use the default 2-fold cross validation,\n",
      "    - integer, to specify the number of folds in a `(Stratified)KFold`,\n",
      "    - An object to be used as a cross-validation generator.\n",
      "    - An iterable yielding train, test splits.\n",
      "    For integer/None inputs, it will use either a `KFold` or\n",
      "    `StratifiedKFold` cross validation depending the value of `stratify`\n",
      "    argument.\n",
      "\n",
      "- `use_features_in_secondary` : bool (default: False)\n",
      "\n",
      "    If True, the meta-classifier will be trained both on the predictions\n",
      "    of the original classifiers and the original dataset.\n",
      "    If False, the meta-classifier will be trained only on the predictions\n",
      "    of the original classifiers.\n",
      "\n",
      "- `stratify` : bool (default: True)\n",
      "\n",
      "    If True, and the `cv` argument is integer it will follow a stratified\n",
      "    K-Fold cross validation technique. If the `cv` argument is a specific\n",
      "    cross validation technique, this argument is omitted.\n",
      "\n",
      "- `shuffle` : bool (default: True)\n",
      "\n",
      "    If True,  and the `cv` argument is integer, the training data will be\n",
      "    shuffled at fitting stage prior to cross-validation. If the `cv`\n",
      "    argument is a specific cross validation technique, this argument is\n",
      "    omitted.\n",
      "\n",
      "- `verbose` : int, optional (default=0)\n",
      "\n",
      "    Controls the verbosity of the building process.\n",
      "    - `verbose=0` (default): Prints nothing\n",
      "    - `verbose=1`: Prints the number & name of the regressor being fitted\n",
      "    and which fold is currently being used for fitting\n",
      "    - `verbose=2`: Prints info about the parameters of the\n",
      "    regressor being fitted\n",
      "    - `verbose>2`: Changes `verbose` param of the underlying regressor to\n",
      "    self.verbose - 2\n",
      "\n",
      "- `store_train_meta_features` : bool (default: False)\n",
      "\n",
      "    If True, the meta-features computed from the training data used\n",
      "    for fitting the meta-classifier stored in the\n",
      "    `self.train_meta_features_` array, which can be\n",
      "    accessed after calling `fit`.\n",
      "\n",
      "- `use_clones` : bool (default: True)\n",
      "\n",
      "    Clones the classifiers for stacking classification if True (default)\n",
      "    or else uses the original ones, which will be refitted on the dataset\n",
      "    upon calling the `fit` method. Hence, if use_clones=True, the original\n",
      "    input classifiers will remain unmodified upon using the\n",
      "    StackingCVClassifier's `fit` method.\n",
      "    Setting `use_clones=False` is\n",
      "    recommended if you are working with estimators that are supporting\n",
      "    the scikit-learn fit/predict API interface but are not compatible\n",
      "    to scikit-learn's `clone` function.\n",
      "\n",
      "\n",
      "**Attributes**\n",
      "\n",
      "- `clfs_` : list, shape=[n_classifiers]\n",
      "\n",
      "    Fitted classifiers (clones of the original classifiers)\n",
      "\n",
      "- `meta_clf_` : estimator\n",
      "\n",
      "    Fitted meta-classifier (clone of the original meta-estimator)\n",
      "\n",
      "- `train_meta_features` : numpy array, shape = [n_samples, n_classifiers]\n",
      "\n",
      "    meta-features for training data, where n_samples is the\n",
      "    number of samples\n",
      "    in training data and n_classifiers is the number of classfiers.\n",
      "\n",
      "**Examples**\n",
      "\n",
      "For usage examples, please see\n",
      "    [http://rasbt.github.io/mlxtend/user_guide/classifier/StackingCVClassifier/](http://rasbt.github.io/mlxtend/user_guide/classifier/StackingCVClassifier/)\n",
      "\n",
      "### Methods\n",
      "\n",
      "<hr>\n",
      "\n",
      "*fit(X, y, groups=None)*\n",
      "\n",
      "Fit ensemble classifers and the meta-classifier.\n",
      "\n",
      "**Parameters**\n",
      "\n",
      "- `X` : numpy array, shape = [n_samples, n_features]\n",
      "\n",
      "    Training vectors, where n_samples is the number of samples and\n",
      "    n_features is the number of features.\n",
      "\n",
      "\n",
      "- `y` : numpy array, shape = [n_samples]\n",
      "\n",
      "    Target values.\n",
      "\n",
      "\n",
      "- `groups` : numpy array/None, shape = [n_samples]\n",
      "\n",
      "    The group that each sample belongs to. This is used by specific\n",
      "    folding strategies such as GroupKFold()\n",
      "\n",
      "**Returns**\n",
      "\n",
      "- `self` : object\n",
      "\n",
      "\n",
      "<hr>\n",
      "\n",
      "*fit_transform(X, y=None, **fit_params)*\n",
      "\n",
      "Fit to data, then transform it.\n",
      "\n",
      "Fits transformer to X and y with optional parameters fit_params\n",
      "and returns a transformed version of X.\n",
      "\n",
      "**Parameters**\n",
      "\n",
      "- `X` : numpy array of shape [n_samples, n_features]\n",
      "\n",
      "    Training set.\n",
      "\n",
      "\n",
      "- `y` : numpy array of shape [n_samples]\n",
      "\n",
      "    Target values.\n",
      "\n",
      "**Returns**\n",
      "\n",
      "- `X_new` : numpy array of shape [n_samples, n_features_new]\n",
      "\n",
      "    Transformed array.\n",
      "\n",
      "<hr>\n",
      "\n",
      "*get_params(deep=True)*\n",
      "\n",
      "Return estimator parameter names for GridSearch support.\n",
      "\n",
      "<hr>\n",
      "\n",
      "*predict(X)*\n",
      "\n",
      "Predict target values for X.\n",
      "\n",
      "**Parameters**\n",
      "\n",
      "- `X` : numpy array, shape = [n_samples, n_features]\n",
      "\n",
      "    Training vectors, where n_samples is the number of samples and\n",
      "    n_features is the number of features.\n",
      "\n",
      "**Returns**\n",
      "\n",
      "- `labels` : array-like, shape = [n_samples]\n",
      "\n",
      "    Predicted class labels.\n",
      "\n",
      "<hr>\n",
      "\n",
      "*predict_meta_features(X)*\n",
      "\n",
      "Get meta-features of test-data.\n",
      "\n",
      "**Parameters**\n",
      "\n",
      "- `X` : numpy array, shape = [n_samples, n_features]\n",
      "\n",
      "    Test vectors, where n_samples is the number of samples and\n",
      "    n_features is the number of features.\n",
      "\n",
      "**Returns**\n",
      "\n",
      "- `meta-features` : numpy array, shape = [n_samples, n_classifiers]\n",
      "\n",
      "    Returns the meta-features for test data.\n",
      "\n",
      "<hr>\n",
      "\n",
      "*predict_proba(X)*\n",
      "\n",
      "Predict class probabilities for X.\n",
      "\n",
      "**Parameters**\n",
      "\n",
      "- `X` : numpy array, shape = [n_samples, n_features]\n",
      "\n",
      "    Training vectors, where n_samples is the number of samples and\n",
      "    n_features is the number of features.\n",
      "\n",
      "**Returns**\n",
      "\n",
      "- `proba` : array-like, shape = [n_samples, n_classes]\n",
      "\n",
      "    Probability for each class per sample.\n",
      "\n",
      "<hr>\n",
      "\n",
      "*score(X, y, sample_weight=None)*\n",
      "\n",
      "Returns the mean accuracy on the given test data and labels.\n",
      "\n",
      "In multi-label classification, this is the subset accuracy\n",
      "which is a harsh metric since you require for each sample that\n",
      "each label set be correctly predicted.\n",
      "\n",
      "**Parameters**\n",
      "\n",
      "- `X` : array-like, shape = (n_samples, n_features)\n",
      "\n",
      "    Test samples.\n",
      "\n",
      "\n",
      "- `y` : array-like, shape = (n_samples) or (n_samples, n_outputs)\n",
      "\n",
      "    True labels for X.\n",
      "\n",
      "\n",
      "- `sample_weight` : array-like, shape = [n_samples], optional\n",
      "\n",
      "    Sample weights.\n",
      "\n",
      "**Returns**\n",
      "\n",
      "- `score` : float\n",
      "\n",
      "    Mean accuracy of self.predict(X) wrt. y.\n",
      "\n",
      "<hr>\n",
      "\n",
      "*set_params(**params)*\n",
      "\n",
      "Set the parameters of this estimator.\n",
      "\n",
      "The method works on simple estimators as well as on nested objects\n",
      "(such as pipelines). The latter have parameters of the form\n",
      "``<component>__<parameter>`` so that it's possible to update each\n",
      "component of a nested object.\n",
      "\n",
      "**Returns**\n",
      "\n",
      "self\n",
      "\n",
      "\n"
     ]
    }
   ],
   "source": [
    "with open('../../api_modules/mlxtend.classifier/StackingCVClassifier.md', 'r') as f:\n",
    "    print(f.read())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  },
  "toc": {
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
